From 02c4473bc8c6b3f9dc616380bbc0e8d2f2629152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Wo=C5=BAniak?= Date: Tue, 26 Apr 2022 19:53:28 +0200 Subject: [PATCH] add res module --- api/src/actors/payment_manager.rs | 33 ++++++---- api/src/model.rs | 6 +- pay_u/src/lib.rs | 102 +++++++++++++++++++----------- 3 files changed, 89 insertions(+), 52 deletions(-) diff --git a/api/src/actors/payment_manager.rs b/api/src/actors/payment_manager.rs index 1497745..e4bb2ae 100644 --- a/api/src/actors/payment_manager.rs +++ b/api/src/actors/payment_manager.rs @@ -3,8 +3,7 @@ use std::sync::Arc; use parking_lot::Mutex; use pay_u::{MerchantPosId, OrderCreateRequest}; -use crate::model; -use crate::model::Product; +use crate::model::{Price, Quantity}; #[macro_export] macro_rules! pay_async_handler { @@ -63,6 +62,7 @@ pub struct PaymentResult { pub payu_order_id: String, } +#[derive(Debug)] pub struct Buyer { /// Required customer e-mail pub email: String, @@ -76,22 +76,29 @@ pub struct Buyer { pub language: String, } -impl From for pay_u::Buyer { +impl From for pay_u::Buyer { fn from(b: Buyer) -> Self { pay_u::Buyer::new(b.email, b.phone, b.first_name, b.last_name, b.language) } } -impl From for pay_u::Product { +#[derive(Debug)] +pub struct Product { + pub name: String, + pub unit_price: Price, + pub quantity: Quantity, +} + +impl From for pay_u::Product { fn from(p: Product) -> Self { - todo!() + pay_u::Product::new(p.name, **p.unit_price, **p.quantity as u32) } } #[derive(Debug, actix::Message)] #[rtype(result = "Result")] pub struct RequestPayment { - pub products: Vec, + pub products: Vec, pub redirect_uri: String, pub currency: String, pub description: String, @@ -104,10 +111,12 @@ pub(crate) async fn request_payment( client: PayUClient, ) -> Result { let mut client = &mut *client.lock(); - client.create_order( - OrderCreateRequest::new(msg.buyer.into(), msg.customer_ip, msg.currency) - .with_description(msg.description) - .with_notify_url(msg.redirect_uri) - .with_products(msg.products.into_iter().map(Into::into)), - ) + Ok(client + .create_order( + OrderCreateRequest::new(msg.buyer.into(), msg.customer_ip, msg.currency) + .with_description(msg.description) + .with_notify_url(msg.redirect_uri) + .with_products(msg.products.into_iter().map(Into::into)), + ) + .await?) } diff --git a/api/src/model.rs b/api/src/model.rs index e723444..72499be 100644 --- a/api/src/model.rs +++ b/api/src/model.rs @@ -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)] #[serde(transparent)] pub struct Price(NonNegative); -#[derive(sqlx::Type, Serialize, Deserialize, Default, Deref, From)] +#[derive(sqlx::Type, Serialize, Deserialize, Default, Debug, Deref, From)] #[sqlx(transparent)] #[serde(transparent)] 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)] #[serde(transparent)] pub struct NonNegative(i32); diff --git a/pay_u/src/lib.rs b/pay_u/src/lib.rs index f8de97d..7742055 100644 --- a/pay_u/src/lib.rs +++ b/pay_u/src/lib.rs @@ -39,6 +39,8 @@ pub enum Error { Refund, #[error("Create order returned invalid response")] CreateOrder, + #[error("Operation failed with {0:?}")] + OpFailed(Status), } pub type Result = std::result::Result; @@ -528,25 +530,29 @@ impl Status { } } -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct CreateOrderResult { - /// Http status as a text - pub status: Status, - /// Client should be redirected to this URI - pub redirect_uri: String, - /// This should be connected to your own order - pub order_id: String, - /// This is YOUR_EXT_ORDER_ID - pub ext_order_id: Option, -} +pub mod result { + use crate::{Refund, Status}; -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -pub struct PartialRefundResult { - pub order_id: Option, - pub refund: Option, - pub status: Status, + #[derive(serde::Deserialize, Debug)] + #[serde(rename_all = "camelCase")] + pub struct CreateOrder { + /// Http status as a text + pub status: Status, + /// Client should be redirected to this URI + pub redirect_uri: String, + /// This should be connected to your own order + pub order_id: String, + /// This is YOUR_EXT_ORDER_ID + pub ext_order_id: Option, + } + + #[derive(serde::Deserialize, Debug)] + #[serde(rename_all = "camelCase")] + pub struct PartialRefund { + pub order_id: Option, + pub refund: Option, + pub status: Status, + } } #[derive(Deserialize, Serialize, Debug)] @@ -570,6 +576,7 @@ impl RefundRequest { pub fn description(&self) -> &str { &self.description } + pub fn amount(&self) -> Price { self.amount } @@ -591,6 +598,8 @@ pub struct Refund { pub mod notify { use serde::Deserialize; + use super::deserialize; + /// Payment notification object received on [super::Order].[notify_url] #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -632,20 +641,31 @@ pub mod notify { #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Order { - #[serde(flatten)] - pub order: super::OrderCreateRequest, + pub notify_url: Option, + /// 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, + /// List of products + pub products: Vec, + #[serde(skip_serializing)] + pub order_create_date: Option, pub pay_method: Option, pub status: super::PaymentStatus, } - impl std::ops::Deref for Order { - type Target = super::OrderCreateRequest; - - fn deref(&self) -> &Self::Target { - &self.order - } - } - #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Prop { @@ -748,7 +768,7 @@ impl Client { /// .await; /// } /// ``` - pub async fn create_order(&mut self, order: OrderCreateRequest) -> Result { + pub async fn create_order(&mut self, order: OrderCreateRequest) -> Result { self.authorize().await?; if order.total_amount != order @@ -779,10 +799,14 @@ impl Client { .text() .await?; 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:?}"); 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 @@ -825,7 +849,7 @@ impl Client { &mut self, order_id: OrderId, refund: RefundRequest, - ) -> Result + ) -> Result where OrderId: std::fmt::Display, { @@ -846,10 +870,14 @@ impl Client { .text() .await?; 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:?}"); Error::Refund - }) + })?; + if res.status.status_code.as_str() != "Success" { + return Err(Error::OpFailed(res.status)); + } + Ok(res) } /// Get or refresh token @@ -953,7 +981,7 @@ mod tests { #[test] fn check_accepted_refund_json() { - let res = serde_json::from_str::(include_str!( + let res = serde_json::from_str::(include_str!( "../tests/responses/accepted_refund.json" )); assert!(res.is_ok()); @@ -995,14 +1023,14 @@ mod tests { } #[test] fn check_rejection_json() { - let res = serde_json::from_str::(include_str!( + let res = serde_json::from_str::(include_str!( "../tests/responses/rejection.json" )); assert!(res.is_ok()); } #[test] fn check_custom_literal_json() { - let res = serde_json::from_str::(include_str!( + let res = serde_json::from_str::(include_str!( "../tests/responses/custom_code_literal.json" )); assert!(res.is_ok());