Payment
This commit is contained in:
parent
791d32d0d8
commit
b4dc801820
@ -35,6 +35,41 @@ macro_rules! db_async_handler {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! query_db {
|
||||||
|
($db: expr, $msg: expr, default $fail: expr) => {
|
||||||
|
match $db.send($msg).await {
|
||||||
|
Ok(Ok(r)) => r,
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
log::error!("{e}");
|
||||||
|
$fail
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{e:?}");
|
||||||
|
$fail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
($db: expr, $msg: expr, $fail: expr) => {
|
||||||
|
$crate::query_db!($db, $msg, $fail, $fail)
|
||||||
|
};
|
||||||
|
|
||||||
|
($db: expr, $msg: expr, $db_fail: expr, $act_fail: expr) => {
|
||||||
|
match $db.send($msg).await {
|
||||||
|
Ok(Ok(r)) => r,
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
log::error!("{e}");
|
||||||
|
return Err($db_fail);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("{e:?}");
|
||||||
|
return Err($act_fail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Failed to connect to database. {0:?}")]
|
#[error("Failed to connect to database. {0:?}")]
|
||||||
|
@ -31,7 +31,7 @@ pub(crate) async fn all_account_orders(
|
|||||||
) -> Result<Vec<AccountOrder>> {
|
) -> Result<Vec<AccountOrder>> {
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
SELECT id, buyer_id, status, order_id, order_ext_id
|
SELECT id, buyer_id, status, order_ext_id
|
||||||
FROM account_orders
|
FROM account_orders
|
||||||
ORDER BY id DESC
|
ORDER BY id DESC
|
||||||
"#,
|
"#,
|
||||||
@ -148,7 +148,7 @@ pub(crate) async fn update_account_order(
|
|||||||
UPDATE account_orders
|
UPDATE account_orders
|
||||||
SET buyer_id = $2 AND status = $3 AND order_id = $4
|
SET buyer_id = $2 AND status = $3 AND order_id = $4
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
RETURNING id, buyer_id, status, order_id, order_ext_id
|
RETURNING id, buyer_id, status, order_ext_id
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.bind(msg.id)
|
.bind(msg.id)
|
||||||
@ -185,7 +185,7 @@ pub(crate) async fn update_account_order_by_ext(
|
|||||||
UPDATE account_orders
|
UPDATE account_orders
|
||||||
SET status = $2
|
SET status = $2
|
||||||
WHERE order_ext_id = $1
|
WHERE order_ext_id = $1
|
||||||
RETURNING id, buyer_id, status, order_id, order_ext_id
|
RETURNING id, buyer_id, status, order_ext_id
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.bind(msg.order_ext_id)
|
.bind(msg.order_ext_id)
|
||||||
@ -209,7 +209,7 @@ db_async_handler!(FindAccountOrder, find_account_order, AccountOrder);
|
|||||||
pub(crate) async fn find_account_order(msg: FindAccountOrder, db: PgPool) -> Result<AccountOrder> {
|
pub(crate) async fn find_account_order(msg: FindAccountOrder, db: PgPool) -> Result<AccountOrder> {
|
||||||
sqlx::query_as(
|
sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
SELECT id, buyer_id, status, order_id, order_ext_id
|
SELECT id, buyer_id, status, order_ext_id
|
||||||
FROM account_orders
|
FROM account_orders
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
"#,
|
"#,
|
||||||
|
@ -2,8 +2,8 @@ use sqlx::PgPool;
|
|||||||
|
|
||||||
use super::Result;
|
use super::Result;
|
||||||
use crate::database::Database;
|
use crate::database::Database;
|
||||||
use crate::db_async_handler;
|
|
||||||
use crate::model::*;
|
use crate::model::*;
|
||||||
|
use crate::{db_async_handler, model};
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
@ -15,6 +15,8 @@ pub enum Error {
|
|||||||
NotExists,
|
NotExists,
|
||||||
#[error("Failed to load all order items")]
|
#[error("Failed to load all order items")]
|
||||||
All,
|
All,
|
||||||
|
#[error("Failed to load order items")]
|
||||||
|
OrderItems,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(actix::Message)]
|
#[derive(actix::Message)]
|
||||||
@ -101,3 +103,29 @@ WHERE id = $1
|
|||||||
super::Error::OrderItem(Error::NotExists)
|
super::Error::OrderItem(Error::NotExists)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(actix::Message)]
|
||||||
|
#[rtype(result = "Result<Vec<OrderItem>>")]
|
||||||
|
pub struct OrderItems {
|
||||||
|
pub order_id: model::AccountOrderId,
|
||||||
|
}
|
||||||
|
|
||||||
|
db_async_handler!(OrderItems, order_items, Vec<OrderItem>);
|
||||||
|
|
||||||
|
pub(crate) async fn order_items(msg: OrderItems, pool: PgPool) -> Result<Vec<OrderItem>> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT id, product_id, order_id, quantity, quantity_unit
|
||||||
|
FROM order_items
|
||||||
|
WHERE order_id = $1
|
||||||
|
ORDER BY id DESC
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(msg.order_id)
|
||||||
|
.fetch_all(&pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
log::error!("{e:?}");
|
||||||
|
super::Error::OrderItem(Error::OrderItems)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -19,6 +19,8 @@ pub enum Error {
|
|||||||
Update,
|
Update,
|
||||||
#[error("Unable to delete product")]
|
#[error("Unable to delete product")]
|
||||||
Delete,
|
Delete,
|
||||||
|
#[error("Unable to find products for shopping cart")]
|
||||||
|
ShoppingCartProducts,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Message)]
|
#[derive(Message)]
|
||||||
@ -168,3 +170,42 @@ RETURNING id,
|
|||||||
database::Error::Product(Error::Delete)
|
database::Error::Product(Error::Delete)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(result = "Result<Vec<model::Product>>")]
|
||||||
|
pub struct ShoppingCartProducts {
|
||||||
|
pub shopping_cart_id: model::ShoppingCartId,
|
||||||
|
}
|
||||||
|
|
||||||
|
crate::db_async_handler!(
|
||||||
|
ShoppingCartProducts,
|
||||||
|
shopping_cart_products,
|
||||||
|
Vec<model::Product>
|
||||||
|
);
|
||||||
|
|
||||||
|
pub(crate) async fn shopping_cart_products(
|
||||||
|
msg: ShoppingCartProducts,
|
||||||
|
pool: PgPool,
|
||||||
|
) -> Result<Vec<Product>> {
|
||||||
|
sqlx::query_as(
|
||||||
|
r#"
|
||||||
|
SELECT products.id,
|
||||||
|
products.name,
|
||||||
|
products.short_description,
|
||||||
|
products.long_description,
|
||||||
|
products.category,
|
||||||
|
products.price,
|
||||||
|
products.deliver_days_flag
|
||||||
|
FROM products
|
||||||
|
INNER JOIN shopping_cart_items ON shopping_cart_items.product_id = products.id
|
||||||
|
WHERE shopping_cart_id = $1
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(msg.shopping_cart_id)
|
||||||
|
.fetch_all(&pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
log::error!("{e:?}");
|
||||||
|
database::Error::Product(Error::ShoppingCartProducts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use actix::Addr;
|
use actix::Addr;
|
||||||
@ -5,10 +6,8 @@ use parking_lot::Mutex;
|
|||||||
|
|
||||||
use crate::config::SharedAppConfig;
|
use crate::config::SharedAppConfig;
|
||||||
use crate::database::Database;
|
use crate::database::Database;
|
||||||
use crate::model::{
|
use crate::model::{AccountId, OrderStatus, Price, ProductId, Quantity, QuantityUnit};
|
||||||
AccountId, OrderStatus, Price, ProductId, Quantity, QuantityUnit, ShoppingCartId,
|
use crate::{database, model, query_db};
|
||||||
};
|
|
||||||
use crate::{database, model};
|
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! pay_async_handler {
|
macro_rules! pay_async_handler {
|
||||||
@ -20,7 +19,43 @@ macro_rules! pay_async_handler {
|
|||||||
use actix::WrapFuture;
|
use actix::WrapFuture;
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
let db = self.db.clone();
|
let db = self.db.clone();
|
||||||
Box::pin(async { $async(msg, client, db).await }.into_actor(self))
|
let config = self.config.clone();
|
||||||
|
Box::pin(async { $async(msg, client, db, config).await }.into_actor(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! query_pay {
|
||||||
|
($manager: expr, $msg: expr, default $fail: expr) => {
|
||||||
|
match $manager.send($msg).await {
|
||||||
|
Ok(Ok(r)) => r,
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
log::error!("Payment {e}");
|
||||||
|
$fail
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Payment {e:?}");
|
||||||
|
$fail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
($manager: expr, $msg: expr, $fail: expr) => {
|
||||||
|
$crate::query_pay!($manager, $msg, $fail, $fail)
|
||||||
|
};
|
||||||
|
|
||||||
|
($manager: expr, $msg: expr, $db_fail: expr, $act_fail: expr) => {
|
||||||
|
match $manager.send($msg).await {
|
||||||
|
Ok(Ok(r)) => r,
|
||||||
|
Ok(Err(e)) => {
|
||||||
|
log::error!("Payment {e}");
|
||||||
|
return Err($db_fail);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Payment {e:?}");
|
||||||
|
return Err($act_fail);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -34,6 +69,8 @@ pub enum Error {
|
|||||||
PayU(#[from] pay_u::Error),
|
PayU(#[from] pay_u::Error),
|
||||||
#[error("Failed to create order")]
|
#[error("Failed to create order")]
|
||||||
CreateOrder,
|
CreateOrder,
|
||||||
|
#[error("Failed to create order. Shopping cart is not available")]
|
||||||
|
UnavailableShoppingCart,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
@ -42,19 +79,25 @@ pub type Result<T> = std::result::Result<T, Error>;
|
|||||||
pub struct PaymentManager {
|
pub struct PaymentManager {
|
||||||
client: PayUClient,
|
client: PayUClient,
|
||||||
db: Addr<Database>,
|
db: Addr<Database>,
|
||||||
|
config: SharedAppConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PaymentManager {
|
impl PaymentManager {
|
||||||
pub async fn build(config: SharedAppConfig, db: Addr<Database>) -> Result<Self> {
|
pub async fn build(config: SharedAppConfig, db: Addr<Database>) -> Result<Self> {
|
||||||
let mut client = pay_u::Client::new(
|
let mut client = {
|
||||||
config.lock().payment().payu_client_id(),
|
let l = config.lock();
|
||||||
config.lock().payment().payu_client_secret(),
|
let p = l.payment();
|
||||||
config.lock().payment().payu_client_merchant_id(),
|
pay_u::Client::new(
|
||||||
);
|
p.payu_client_id(),
|
||||||
|
p.payu_client_secret(),
|
||||||
|
p.payu_client_merchant_id(),
|
||||||
|
)
|
||||||
|
};
|
||||||
client.authorize().await?;
|
client.authorize().await?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
client: Arc::new(Mutex::new(client)),
|
client: Arc::new(Mutex::new(client)),
|
||||||
db,
|
db,
|
||||||
|
config,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -103,55 +146,99 @@ impl From<Product> for pay_u::Product {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct CreatePaymentResult {
|
||||||
|
pub order: model::AccountOrder,
|
||||||
|
pub items: Vec<model::OrderItem>,
|
||||||
|
pub redirect_uri: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, actix::Message)]
|
#[derive(Debug, actix::Message)]
|
||||||
#[rtype(result = "Result<pay_u::OrderId>")]
|
#[rtype(result = "Result<CreatePaymentResult>")]
|
||||||
pub struct RequestPayment {
|
pub struct RequestPayment {
|
||||||
pub products: Vec<Product>,
|
|
||||||
pub currency: String,
|
pub currency: String,
|
||||||
pub description: String,
|
|
||||||
pub buyer: Buyer,
|
pub buyer: Buyer,
|
||||||
pub customer_ip: String,
|
pub customer_ip: String,
|
||||||
pub buyer_id: AccountId,
|
pub buyer_id: AccountId,
|
||||||
pub shopping_cart_id: ShoppingCartId,
|
|
||||||
pub redirect_uri: String,
|
|
||||||
pub continue_uri: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pay_async_handler!(RequestPayment, request_payment, pay_u::OrderId);
|
pay_async_handler!(RequestPayment, request_payment, CreatePaymentResult);
|
||||||
|
|
||||||
pub(crate) async fn request_payment(
|
pub(crate) async fn request_payment(
|
||||||
msg: RequestPayment,
|
msg: RequestPayment,
|
||||||
client: PayUClient,
|
client: PayUClient,
|
||||||
db: Addr<Database>,
|
db: Addr<Database>,
|
||||||
) -> Result<pay_u::OrderId> {
|
config: SharedAppConfig,
|
||||||
let db_order: model::AccountOrder = match db
|
) -> Result<CreatePaymentResult> {
|
||||||
.send(database::CreateAccountOrder {
|
let (notify_uri, continue_uri) = {
|
||||||
buyer_id: msg.buyer_id,
|
let l = config.lock();
|
||||||
items: msg
|
let w = l.web();
|
||||||
.products
|
(
|
||||||
|
format!("{}/api/v1/payment/notify", w.host()),
|
||||||
|
format!("{}/payment/success", w.host()),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let cart: model::ShoppingCart = query_db!(
|
||||||
|
db,
|
||||||
|
database::EnsureActiveShoppingCart {
|
||||||
|
buyer_id: msg.buyer_id
|
||||||
|
},
|
||||||
|
Error::UnavailableShoppingCart
|
||||||
|
);
|
||||||
|
let cart_items: Vec<model::ShoppingCartItem> = query_db!(
|
||||||
|
db,
|
||||||
|
database::CartItems {
|
||||||
|
shopping_cart_id: cart.id,
|
||||||
|
},
|
||||||
|
Error::UnavailableShoppingCart
|
||||||
|
);
|
||||||
|
let mut items =
|
||||||
|
cart_items
|
||||||
.iter()
|
.iter()
|
||||||
.map(|product| database::create_order::OrderItem {
|
.fold(HashMap::with_capacity(cart_items.len()), |mut agg, item| {
|
||||||
|
agg.insert(item.product_id, (item.quantity, item.quantity_unit));
|
||||||
|
agg
|
||||||
|
});
|
||||||
|
|
||||||
|
let cart_products: Vec<model::Product> = query_db!(
|
||||||
|
db,
|
||||||
|
database::ShoppingCartProducts {
|
||||||
|
shopping_cart_id: cart.id,
|
||||||
|
},
|
||||||
|
Error::UnavailableShoppingCart
|
||||||
|
);
|
||||||
|
|
||||||
|
let db_order: model::AccountOrder = query_db!(
|
||||||
|
db,
|
||||||
|
database::CreateAccountOrder {
|
||||||
|
buyer_id: msg.buyer_id,
|
||||||
|
items: cart_products
|
||||||
|
.iter()
|
||||||
|
.map(|product| {
|
||||||
|
let (quantity, quantity_unit) =
|
||||||
|
items.get(&product.id).cloned().unwrap_or_else(|| {
|
||||||
|
(
|
||||||
|
model::Quantity::try_from(0).unwrap(),
|
||||||
|
model::QuantityUnit::Gram,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
database::create_order::OrderItem {
|
||||||
product_id: product.id,
|
product_id: product.id,
|
||||||
quantity: product.quantity,
|
quantity,
|
||||||
quantity_unit: product.quantity_unit,
|
quantity_unit,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
shopping_cart_id: msg.shopping_cart_id,
|
shopping_cart_id: cart.id,
|
||||||
})
|
},
|
||||||
.await
|
Error::CreateOrder
|
||||||
{
|
);
|
||||||
Ok(Ok(order)) => order,
|
|
||||||
Ok(Err(e)) => {
|
|
||||||
log::error!("{e}");
|
|
||||||
return Err(Error::CreateOrder);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("{e:?}");
|
|
||||||
return Err(Error::CreateOrder);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let order = {
|
let pay_u::res::CreateOrder {
|
||||||
|
status: _,
|
||||||
|
redirect_uri,
|
||||||
|
order_id: _,
|
||||||
|
ext_order_id: _,
|
||||||
|
} = {
|
||||||
client
|
client
|
||||||
.lock()
|
.lock()
|
||||||
.create_order(
|
.create_order(
|
||||||
@ -159,16 +246,38 @@ pub(crate) async fn request_payment(
|
|||||||
msg.buyer.into(),
|
msg.buyer.into(),
|
||||||
msg.customer_ip,
|
msg.customer_ip,
|
||||||
msg.currency,
|
msg.currency,
|
||||||
msg.description,
|
format!("Order #{}", db_order.id),
|
||||||
)?
|
)?
|
||||||
.with_products(msg.products.into_iter().map(Into::into))
|
.with_products(cart_products.into_iter().map(|p| {
|
||||||
|
pay_u::Product::new(
|
||||||
|
p.name.to_string(),
|
||||||
|
**p.price,
|
||||||
|
items
|
||||||
|
.remove(&p.id)
|
||||||
|
.map(|(quantity, _)| **quantity as u32)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
)
|
||||||
|
}))
|
||||||
.with_ext_order_id(db_order.order_ext_id.to_string())
|
.with_ext_order_id(db_order.order_ext_id.to_string())
|
||||||
.with_notify_url(msg.redirect_uri)
|
.with_notify_url(notify_uri)
|
||||||
.with_continue_url(msg.continue_uri),
|
.with_continue_url(continue_uri),
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
};
|
};
|
||||||
Ok(order.order_id)
|
|
||||||
|
let order_items = query_db!(
|
||||||
|
db,
|
||||||
|
database::OrderItems {
|
||||||
|
order_id: db_order.id
|
||||||
|
},
|
||||||
|
Error::CreateOrder
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(CreatePaymentResult {
|
||||||
|
order: db_order,
|
||||||
|
items: order_items,
|
||||||
|
redirect_uri,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize)]
|
#[derive(Debug, serde::Deserialize)]
|
||||||
@ -187,6 +296,7 @@ pub(crate) async fn update_payment(
|
|||||||
msg: UpdatePayment,
|
msg: UpdatePayment,
|
||||||
_client: PayUClient,
|
_client: PayUClient,
|
||||||
db: Addr<Database>,
|
db: Addr<Database>,
|
||||||
|
_config: SharedAppConfig,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let status = msg.notification.0.status();
|
let status = msg.notification.0.status();
|
||||||
let order_ext_id = match msg.notification.0.order.ext_order_id {
|
let order_ext_id = match msg.notification.0.order.ext_order_id {
|
||||||
|
@ -139,11 +139,16 @@ impl WebConfig {
|
|||||||
.expect("Invalid password hash")
|
.expect("Invalid password hash")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_secret(&self) -> String {
|
pub fn session_secret(&self) -> actix_web::cookie::Key {
|
||||||
|
use actix_web::cookie::Key;
|
||||||
self.session_secret
|
self.session_secret
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.cloned()
|
.map(|s| Key::from(s.as_bytes()))
|
||||||
.or_else(|| std::env::var("SESSION_SECRET").ok())
|
.or_else(|| {
|
||||||
|
std::env::var("SESSION_SECRET")
|
||||||
|
.ok()
|
||||||
|
.map(|s| Key::from(s.as_bytes()))
|
||||||
|
})
|
||||||
.expect("Web config session_secret nor SESSION_SECRET env was given")
|
.expect("Web config session_secret nor SESSION_SECRET env was given")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ use std::io::Write;
|
|||||||
use actix::Actor;
|
use actix::Actor;
|
||||||
use actix_session::storage::RedisActorSessionStore;
|
use actix_session::storage::RedisActorSessionStore;
|
||||||
use actix_session::SessionMiddleware;
|
use actix_session::SessionMiddleware;
|
||||||
use actix_web::cookie::Key;
|
|
||||||
use actix_web::middleware::Logger;
|
use actix_web::middleware::Logger;
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use actix_web::{App, HttpServer};
|
use actix_web::{App, HttpServer};
|
||||||
@ -48,11 +47,6 @@ pub enum Error {
|
|||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
async fn server(opts: ServerOpts) -> Result<()> {
|
async fn server(opts: ServerOpts) -> Result<()> {
|
||||||
let secret_key = {
|
|
||||||
let key_secret = std::env::var("SESSION_SECRET")
|
|
||||||
.expect("session requires secret key with 64 or more characters");
|
|
||||||
Key::from(key_secret.as_bytes())
|
|
||||||
};
|
|
||||||
let redis_connection_string = "127.0.0.1:6379";
|
let redis_connection_string = "127.0.0.1:6379";
|
||||||
|
|
||||||
let app_config = config::default_load(&opts);
|
let app_config = config::default_load(&opts);
|
||||||
@ -64,21 +58,27 @@ async fn server(opts: ServerOpts) -> Result<()> {
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to start payment manager")
|
.expect("Failed to start payment manager")
|
||||||
.start();
|
.start();
|
||||||
let addr = (
|
let addr = {
|
||||||
app_config.lock().web().bind().unwrap_or(opts.bind),
|
let l = app_config.lock();
|
||||||
app_config.lock().web().port().unwrap_or(opts.port),
|
let w = l.web();
|
||||||
);
|
(w.bind().unwrap_or(opts.bind), w.port().unwrap_or(opts.port))
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Listen at {:?}", addr);
|
||||||
|
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
let config = app_config.clone();
|
let config = app_config.clone();
|
||||||
App::new()
|
App::new()
|
||||||
.wrap(Logger::default())
|
.wrap(Logger::default())
|
||||||
.wrap(actix_web::middleware::Compress::default())
|
|
||||||
.wrap(actix_web::middleware::NormalizePath::trim())
|
|
||||||
.wrap(SessionMiddleware::new(
|
.wrap(SessionMiddleware::new(
|
||||||
RedisActorSessionStore::new(redis_connection_string),
|
RedisActorSessionStore::new(redis_connection_string),
|
||||||
secret_key.clone(),
|
{
|
||||||
|
let l = config.lock();
|
||||||
|
l.web().session_secret()
|
||||||
|
},
|
||||||
))
|
))
|
||||||
|
.wrap(actix_web::middleware::Compress::default())
|
||||||
|
.wrap(actix_web::middleware::NormalizePath::trim())
|
||||||
.app_data(Data::new(config))
|
.app_data(Data::new(config))
|
||||||
.app_data(Data::new(db.clone()))
|
.app_data(Data::new(db.clone()))
|
||||||
.app_data(Data::new(token_manager.clone()))
|
.app_data(Data::new(token_manager.clone()))
|
||||||
|
@ -456,7 +456,20 @@ impl From<FullAccount> for Account {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(sqlx::Type, Serialize, Deserialize, Debug, Copy, Clone, Deref, Display, From)]
|
#[derive(
|
||||||
|
sqlx::Type,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
Debug,
|
||||||
|
Copy,
|
||||||
|
Clone,
|
||||||
|
PartialEq,
|
||||||
|
Eq,
|
||||||
|
Hash,
|
||||||
|
Deref,
|
||||||
|
Display,
|
||||||
|
From,
|
||||||
|
)]
|
||||||
#[sqlx(transparent)]
|
#[sqlx(transparent)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
pub struct ProductId(RecordId);
|
pub struct ProductId(RecordId);
|
||||||
@ -592,6 +605,12 @@ pub struct ShoppingCartItem {
|
|||||||
pub quantity_unit: QuantityUnit,
|
pub quantity_unit: QuantityUnit,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ShoppingCartItem {
|
||||||
|
pub fn quantity(&self) -> Quantity {
|
||||||
|
self.quantity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Deref, Display, Debug)]
|
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Deref, Display, Debug)]
|
||||||
#[sqlx(transparent)]
|
#[sqlx(transparent)]
|
||||||
#[serde(transparent)]
|
#[serde(transparent)]
|
||||||
|
@ -34,6 +34,29 @@ impl From<(Vec<model::AccountOrder>, Vec<model::OrderItem>)> for AccountOrders {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<(model::AccountOrder, Vec<model::OrderItem>)> for AccountOrder {
|
||||||
|
fn from(
|
||||||
|
(
|
||||||
|
model::AccountOrder {
|
||||||
|
id,
|
||||||
|
buyer_id,
|
||||||
|
status,
|
||||||
|
order_id,
|
||||||
|
order_ext_id: _,
|
||||||
|
},
|
||||||
|
mut items,
|
||||||
|
): (model::AccountOrder, Vec<model::OrderItem>),
|
||||||
|
) -> Self {
|
||||||
|
AccountOrder {
|
||||||
|
id,
|
||||||
|
buyer_id,
|
||||||
|
status,
|
||||||
|
order_id,
|
||||||
|
items: items.drain_filter(|item| item.order_id == id).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Debug)]
|
||||||
pub struct AccountOrder {
|
pub struct AccountOrder {
|
||||||
pub id: AccountOrderId,
|
pub id: AccountOrderId,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use actix::Addr;
|
use actix::Addr;
|
||||||
use actix_session::Session;
|
use actix_session::Session;
|
||||||
|
use actix_web::get;
|
||||||
use actix_web::web::{Data, Json, ServiceConfig};
|
use actix_web::web::{Data, Json, ServiceConfig};
|
||||||
use actix_web::{delete, get, patch, post};
|
|
||||||
|
|
||||||
use crate::database::Database;
|
use crate::database::Database;
|
||||||
use crate::model::api::AccountOrders;
|
use crate::model::api::AccountOrders;
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
use actix::Addr;
|
use actix::Addr;
|
||||||
use actix_web::web::{scope, Data, Json, ServiceConfig};
|
use actix_web::web::{scope, Data, Json, ServiceConfig};
|
||||||
use actix_web::{delete, get, post, HttpResponse};
|
use actix_web::{delete, get, post, HttpRequest, HttpResponse};
|
||||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||||
|
|
||||||
use crate::actors::cart_manager;
|
use crate::actors::cart_manager;
|
||||||
use crate::actors::cart_manager::CartManager;
|
use crate::actors::cart_manager::CartManager;
|
||||||
use crate::database::Database;
|
use crate::database::Database;
|
||||||
use crate::model::{
|
use crate::model::{
|
||||||
AccountId, ProductId, Quantity, QuantityUnit, ShoppingCart, ShoppingCartId, ShoppingCartItem,
|
AccountId, ProductId, Quantity, QuantityUnit, ShoppingCart, ShoppingCartItem,
|
||||||
ShoppingCartItemId,
|
ShoppingCartItemId,
|
||||||
};
|
};
|
||||||
use crate::order_manager::OrderManager;
|
use crate::payment_manager::PaymentManager;
|
||||||
use crate::routes::public::api_v1::ShoppingCartError;
|
use crate::routes::public::api_v1::ShoppingCartError;
|
||||||
use crate::routes::public::Error as PublicError;
|
use crate::routes::public::Error as PublicError;
|
||||||
use crate::routes::{RequireUser, Result};
|
use crate::routes::{RequireUser, Result};
|
||||||
use crate::token_manager::TokenManager;
|
use crate::token_manager::TokenManager;
|
||||||
use crate::{database, order_manager, routes};
|
use crate::{database, model, payment_manager, query_pay, routes};
|
||||||
|
|
||||||
#[get("/shopping-cart")]
|
#[get("/shopping-cart")]
|
||||||
async fn shopping_cart(
|
async fn shopping_cart(
|
||||||
@ -190,38 +190,77 @@ async fn delete_cart_item(
|
|||||||
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
pub struct CreateOrderInput {
|
pub struct CreateOrderInput {
|
||||||
pub shopping_cart_id: ShoppingCartId,
|
/// Required customer e-mail
|
||||||
|
pub email: String,
|
||||||
|
/// Required customer phone number
|
||||||
|
pub phone: String,
|
||||||
|
/// Required customer first name
|
||||||
|
pub first_name: String,
|
||||||
|
/// Required customer last name
|
||||||
|
pub last_name: String,
|
||||||
|
/// Required customer language
|
||||||
|
pub language: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/order")]
|
#[post("/order")]
|
||||||
pub(crate) async fn create_order(
|
pub(crate) async fn create_order(
|
||||||
|
req: HttpRequest,
|
||||||
Json(payload): Json<CreateOrderInput>,
|
Json(payload): Json<CreateOrderInput>,
|
||||||
tm: Data<Addr<TokenManager>>,
|
tm: Data<Addr<TokenManager>>,
|
||||||
credentials: BearerAuth,
|
credentials: BearerAuth,
|
||||||
om: Data<Addr<OrderManager>>,
|
payment: Data<Addr<PaymentManager>>,
|
||||||
) -> routes::Result<HttpResponse> {
|
) -> routes::Result<HttpResponse> {
|
||||||
let (token, _) = credentials.require_user(tm.into_inner()).await?;
|
let (
|
||||||
let order = match om
|
model::Token {
|
||||||
.send(order_manager::CreateOrder {
|
id: _,
|
||||||
account_id: AccountId::from(token.subject),
|
customer_id: _,
|
||||||
shopping_cart_id: payload.shopping_cart_id,
|
role: _,
|
||||||
})
|
issuer: _,
|
||||||
.await
|
subject,
|
||||||
{
|
audience: _,
|
||||||
Ok(Ok(order)) => order,
|
expiration_time: _,
|
||||||
Ok(Err(e)) => {
|
not_before_time: _,
|
||||||
log::error!("{e}");
|
issued_at_time: _,
|
||||||
return Err(routes::Error::Public(PublicError::ApiV1(
|
jwt_id: _,
|
||||||
super::Error::AddOrder,
|
},
|
||||||
)));
|
_,
|
||||||
}
|
) = credentials.require_user(tm.into_inner()).await?;
|
||||||
Err(e) => {
|
|
||||||
log::error!("{e}");
|
let buyer_id = model::AccountId::from(subject);
|
||||||
return Err(routes::Error::Public(PublicError::DatabaseConnection));
|
let CreateOrderInput {
|
||||||
}
|
email,
|
||||||
|
phone,
|
||||||
|
first_name,
|
||||||
|
last_name,
|
||||||
|
language,
|
||||||
|
} = payload;
|
||||||
|
let ip = match req.peer_addr() {
|
||||||
|
Some(ip) => ip,
|
||||||
|
_ => return Ok(HttpResponse::BadRequest().body("No IP")),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(HttpResponse::Created().json(order))
|
let payment_manager::CreatePaymentResult { redirect_uri, .. } = query_pay!(
|
||||||
|
payment,
|
||||||
|
payment_manager::RequestPayment {
|
||||||
|
currency: "PLN".to_string(),
|
||||||
|
buyer: payment_manager::Buyer {
|
||||||
|
email,
|
||||||
|
phone,
|
||||||
|
first_name,
|
||||||
|
last_name,
|
||||||
|
language,
|
||||||
|
},
|
||||||
|
customer_ip: ip.to_string(),
|
||||||
|
buyer_id,
|
||||||
|
},
|
||||||
|
routes::Error::Public(PublicError::DatabaseConnection)
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(HttpResponse::SeeOther()
|
||||||
|
.append_header(("Location", redirect_uri.as_str()))
|
||||||
|
.body(format!(
|
||||||
|
"<a href=\"{redirect_uri}\">Go to {redirect_uri}</a>"
|
||||||
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn configure(config: &mut ServiceConfig) {
|
pub(crate) fn configure(config: &mut ServiceConfig) {
|
||||||
|
@ -85,7 +85,7 @@ async fn sign_in(
|
|||||||
Ok(HttpResponse::Created().json(SignInOutput { token: string }))
|
Ok(HttpResponse::Created().json(SignInOutput { token: string }))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/pay_u/notify")]
|
#[post("/payment/notify")]
|
||||||
async fn handle_notification(
|
async fn handle_notification(
|
||||||
Json(notify): Json<PaymentNotification>,
|
Json(notify): Json<PaymentNotification>,
|
||||||
payment: Data<Addr<PaymentManager>>,
|
payment: Data<Addr<PaymentManager>>,
|
||||||
|
Loading…
Reference in New Issue
Block a user