add res module
This commit is contained in:
parent
cbc2a30e0e
commit
02c4473bc8
@ -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
|
||||||
OrderCreateRequest::new(msg.buyer.into(), msg.customer_ip, msg.currency)
|
.create_order(
|
||||||
.with_description(msg.description)
|
OrderCreateRequest::new(msg.buyer.into(), msg.customer_ip, msg.currency)
|
||||||
.with_notify_url(msg.redirect_uri)
|
.with_description(msg.description)
|
||||||
.with_products(msg.products.into_iter().map(Into::into)),
|
.with_notify_url(msg.redirect_uri)
|
||||||
)
|
.with_products(msg.products.into_iter().map(Into::into)),
|
||||||
|
)
|
||||||
|
.await?)
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
102
pay_u/src/lib.rs
102
pay_u/src/lib.rs
@ -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,25 +530,29 @@ impl Status {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
pub mod result {
|
||||||
#[serde(rename_all = "camelCase")]
|
use crate::{Refund, Status};
|
||||||
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<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(serde::Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PartialRefundResult {
|
pub struct CreateOrder {
|
||||||
pub order_id: Option<String>,
|
/// Http status as a text
|
||||||
pub refund: Option<Refund>,
|
pub status: Status,
|
||||||
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<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct PartialRefund {
|
||||||
|
pub order_id: Option<String>,
|
||||||
|
pub refund: Option<Refund>,
|
||||||
|
pub status: Status,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
@ -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());
|
||||||
|
Loading…
Reference in New Issue
Block a user