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)]
|
||||
pub enum Error {
|
||||
#[error("Failed to connect to database. {0:?}")]
|
||||
|
@ -31,7 +31,7 @@ pub(crate) async fn all_account_orders(
|
||||
) -> Result<Vec<AccountOrder>> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, buyer_id, status, order_id, order_ext_id
|
||||
SELECT id, buyer_id, status, order_ext_id
|
||||
FROM account_orders
|
||||
ORDER BY id DESC
|
||||
"#,
|
||||
@ -148,7 +148,7 @@ pub(crate) async fn update_account_order(
|
||||
UPDATE account_orders
|
||||
SET buyer_id = $2 AND status = $3 AND order_id = $4
|
||||
WHERE id = $1
|
||||
RETURNING id, buyer_id, status, order_id, order_ext_id
|
||||
RETURNING id, buyer_id, status, order_ext_id
|
||||
"#,
|
||||
)
|
||||
.bind(msg.id)
|
||||
@ -185,7 +185,7 @@ pub(crate) async fn update_account_order_by_ext(
|
||||
UPDATE account_orders
|
||||
SET status = $2
|
||||
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)
|
||||
@ -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> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
SELECT id, buyer_id, status, order_id, order_ext_id
|
||||
SELECT id, buyer_id, status, order_ext_id
|
||||
FROM account_orders
|
||||
WHERE id = $1
|
||||
"#,
|
||||
|
@ -2,8 +2,8 @@ use sqlx::PgPool;
|
||||
|
||||
use super::Result;
|
||||
use crate::database::Database;
|
||||
use crate::db_async_handler;
|
||||
use crate::model::*;
|
||||
use crate::{db_async_handler, model};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
@ -15,6 +15,8 @@ pub enum Error {
|
||||
NotExists,
|
||||
#[error("Failed to load all order items")]
|
||||
All,
|
||||
#[error("Failed to load order items")]
|
||||
OrderItems,
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
@ -101,3 +103,29 @@ WHERE id = $1
|
||||
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,
|
||||
#[error("Unable to delete product")]
|
||||
Delete,
|
||||
#[error("Unable to find products for shopping cart")]
|
||||
ShoppingCartProducts,
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
@ -168,3 +170,42 @@ RETURNING id,
|
||||
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 actix::Addr;
|
||||
@ -5,10 +6,8 @@ use parking_lot::Mutex;
|
||||
|
||||
use crate::config::SharedAppConfig;
|
||||
use crate::database::Database;
|
||||
use crate::model::{
|
||||
AccountId, OrderStatus, Price, ProductId, Quantity, QuantityUnit, ShoppingCartId,
|
||||
};
|
||||
use crate::{database, model};
|
||||
use crate::model::{AccountId, OrderStatus, Price, ProductId, Quantity, QuantityUnit};
|
||||
use crate::{database, model, query_db};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! pay_async_handler {
|
||||
@ -20,7 +19,43 @@ macro_rules! pay_async_handler {
|
||||
use actix::WrapFuture;
|
||||
let client = self.client.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),
|
||||
#[error("Failed to create order")]
|
||||
CreateOrder,
|
||||
#[error("Failed to create order. Shopping cart is not available")]
|
||||
UnavailableShoppingCart,
|
||||
}
|
||||
|
||||
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 {
|
||||
client: PayUClient,
|
||||
db: Addr<Database>,
|
||||
config: SharedAppConfig,
|
||||
}
|
||||
|
||||
impl PaymentManager {
|
||||
pub async fn build(config: SharedAppConfig, db: Addr<Database>) -> Result<Self> {
|
||||
let mut client = pay_u::Client::new(
|
||||
config.lock().payment().payu_client_id(),
|
||||
config.lock().payment().payu_client_secret(),
|
||||
config.lock().payment().payu_client_merchant_id(),
|
||||
);
|
||||
let mut client = {
|
||||
let l = config.lock();
|
||||
let p = l.payment();
|
||||
pay_u::Client::new(
|
||||
p.payu_client_id(),
|
||||
p.payu_client_secret(),
|
||||
p.payu_client_merchant_id(),
|
||||
)
|
||||
};
|
||||
client.authorize().await?;
|
||||
Ok(Self {
|
||||
client: Arc::new(Mutex::new(client)),
|
||||
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)]
|
||||
#[rtype(result = "Result<pay_u::OrderId>")]
|
||||
#[rtype(result = "Result<CreatePaymentResult>")]
|
||||
pub struct RequestPayment {
|
||||
pub products: Vec<Product>,
|
||||
pub currency: String,
|
||||
pub description: String,
|
||||
pub buyer: Buyer,
|
||||
pub customer_ip: String,
|
||||
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(
|
||||
msg: RequestPayment,
|
||||
client: PayUClient,
|
||||
db: Addr<Database>,
|
||||
) -> Result<pay_u::OrderId> {
|
||||
let db_order: model::AccountOrder = match db
|
||||
.send(database::CreateAccountOrder {
|
||||
config: SharedAppConfig,
|
||||
) -> Result<CreatePaymentResult> {
|
||||
let (notify_uri, continue_uri) = {
|
||||
let l = config.lock();
|
||||
let w = l.web();
|
||||
(
|
||||
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()
|
||||
.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: msg
|
||||
.products
|
||||
items: cart_products
|
||||
.iter()
|
||||
.map(|product| database::create_order::OrderItem {
|
||||
product_id: product.id,
|
||||
quantity: product.quantity,
|
||||
quantity_unit: product.quantity_unit,
|
||||
.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,
|
||||
quantity,
|
||||
quantity_unit,
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
shopping_cart_id: msg.shopping_cart_id,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(Ok(order)) => order,
|
||||
Ok(Err(e)) => {
|
||||
log::error!("{e}");
|
||||
return Err(Error::CreateOrder);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{e:?}");
|
||||
return Err(Error::CreateOrder);
|
||||
}
|
||||
};
|
||||
shopping_cart_id: cart.id,
|
||||
},
|
||||
Error::CreateOrder
|
||||
);
|
||||
|
||||
let order = {
|
||||
let pay_u::res::CreateOrder {
|
||||
status: _,
|
||||
redirect_uri,
|
||||
order_id: _,
|
||||
ext_order_id: _,
|
||||
} = {
|
||||
client
|
||||
.lock()
|
||||
.create_order(
|
||||
@ -159,16 +246,38 @@ pub(crate) async fn request_payment(
|
||||
msg.buyer.into(),
|
||||
msg.customer_ip,
|
||||
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_notify_url(msg.redirect_uri)
|
||||
.with_continue_url(msg.continue_uri),
|
||||
.with_notify_url(notify_uri)
|
||||
.with_continue_url(continue_uri),
|
||||
)
|
||||
.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)]
|
||||
@ -187,6 +296,7 @@ pub(crate) async fn update_payment(
|
||||
msg: UpdatePayment,
|
||||
_client: PayUClient,
|
||||
db: Addr<Database>,
|
||||
_config: SharedAppConfig,
|
||||
) -> Result<()> {
|
||||
let status = msg.notification.0.status();
|
||||
let order_ext_id = match msg.notification.0.order.ext_order_id {
|
||||
|
@ -139,11 +139,16 @@ impl WebConfig {
|
||||
.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
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.or_else(|| std::env::var("SESSION_SECRET").ok())
|
||||
.map(|s| Key::from(s.as_bytes()))
|
||||
.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")
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ use std::io::Write;
|
||||
use actix::Actor;
|
||||
use actix_session::storage::RedisActorSessionStore;
|
||||
use actix_session::SessionMiddleware;
|
||||
use actix_web::cookie::Key;
|
||||
use actix_web::middleware::Logger;
|
||||
use actix_web::web::Data;
|
||||
use actix_web::{App, HttpServer};
|
||||
@ -48,11 +47,6 @@ pub enum Error {
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
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 app_config = config::default_load(&opts);
|
||||
@ -64,21 +58,27 @@ async fn server(opts: ServerOpts) -> Result<()> {
|
||||
.await
|
||||
.expect("Failed to start payment manager")
|
||||
.start();
|
||||
let addr = (
|
||||
app_config.lock().web().bind().unwrap_or(opts.bind),
|
||||
app_config.lock().web().port().unwrap_or(opts.port),
|
||||
);
|
||||
let addr = {
|
||||
let l = app_config.lock();
|
||||
let w = l.web();
|
||||
(w.bind().unwrap_or(opts.bind), w.port().unwrap_or(opts.port))
|
||||
};
|
||||
|
||||
println!("Listen at {:?}", addr);
|
||||
|
||||
HttpServer::new(move || {
|
||||
let config = app_config.clone();
|
||||
App::new()
|
||||
.wrap(Logger::default())
|
||||
.wrap(actix_web::middleware::Compress::default())
|
||||
.wrap(actix_web::middleware::NormalizePath::trim())
|
||||
.wrap(SessionMiddleware::new(
|
||||
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(db.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)]
|
||||
#[serde(transparent)]
|
||||
pub struct ProductId(RecordId);
|
||||
@ -592,6 +605,12 @@ pub struct ShoppingCartItem {
|
||||
pub quantity_unit: QuantityUnit,
|
||||
}
|
||||
|
||||
impl ShoppingCartItem {
|
||||
pub fn quantity(&self) -> Quantity {
|
||||
self.quantity
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Copy, Clone, Deref, Display, Debug)]
|
||||
#[sqlx(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)]
|
||||
pub struct AccountOrder {
|
||||
pub id: AccountOrderId,
|
||||
|
@ -1,7 +1,7 @@
|
||||
use actix::Addr;
|
||||
use actix_session::Session;
|
||||
use actix_web::get;
|
||||
use actix_web::web::{Data, Json, ServiceConfig};
|
||||
use actix_web::{delete, get, patch, post};
|
||||
|
||||
use crate::database::Database;
|
||||
use crate::model::api::AccountOrders;
|
||||
|
@ -1,21 +1,21 @@
|
||||
use actix::Addr;
|
||||
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 crate::actors::cart_manager;
|
||||
use crate::actors::cart_manager::CartManager;
|
||||
use crate::database::Database;
|
||||
use crate::model::{
|
||||
AccountId, ProductId, Quantity, QuantityUnit, ShoppingCart, ShoppingCartId, ShoppingCartItem,
|
||||
AccountId, ProductId, Quantity, QuantityUnit, ShoppingCart, ShoppingCartItem,
|
||||
ShoppingCartItemId,
|
||||
};
|
||||
use crate::order_manager::OrderManager;
|
||||
use crate::payment_manager::PaymentManager;
|
||||
use crate::routes::public::api_v1::ShoppingCartError;
|
||||
use crate::routes::public::Error as PublicError;
|
||||
use crate::routes::{RequireUser, Result};
|
||||
use crate::token_manager::TokenManager;
|
||||
use crate::{database, order_manager, routes};
|
||||
use crate::{database, model, payment_manager, query_pay, routes};
|
||||
|
||||
#[get("/shopping-cart")]
|
||||
async fn shopping_cart(
|
||||
@ -190,38 +190,77 @@ async fn delete_cart_item(
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
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")]
|
||||
pub(crate) async fn create_order(
|
||||
req: HttpRequest,
|
||||
Json(payload): Json<CreateOrderInput>,
|
||||
tm: Data<Addr<TokenManager>>,
|
||||
credentials: BearerAuth,
|
||||
om: Data<Addr<OrderManager>>,
|
||||
payment: Data<Addr<PaymentManager>>,
|
||||
) -> routes::Result<HttpResponse> {
|
||||
let (token, _) = credentials.require_user(tm.into_inner()).await?;
|
||||
let order = match om
|
||||
.send(order_manager::CreateOrder {
|
||||
account_id: AccountId::from(token.subject),
|
||||
shopping_cart_id: payload.shopping_cart_id,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(Ok(order)) => order,
|
||||
Ok(Err(e)) => {
|
||||
log::error!("{e}");
|
||||
return Err(routes::Error::Public(PublicError::ApiV1(
|
||||
super::Error::AddOrder,
|
||||
)));
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{e}");
|
||||
return Err(routes::Error::Public(PublicError::DatabaseConnection));
|
||||
}
|
||||
let (
|
||||
model::Token {
|
||||
id: _,
|
||||
customer_id: _,
|
||||
role: _,
|
||||
issuer: _,
|
||||
subject,
|
||||
audience: _,
|
||||
expiration_time: _,
|
||||
not_before_time: _,
|
||||
issued_at_time: _,
|
||||
jwt_id: _,
|
||||
},
|
||||
_,
|
||||
) = credentials.require_user(tm.into_inner()).await?;
|
||||
|
||||
let buyer_id = model::AccountId::from(subject);
|
||||
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) {
|
||||
|
@ -85,7 +85,7 @@ async fn sign_in(
|
||||
Ok(HttpResponse::Created().json(SignInOutput { token: string }))
|
||||
}
|
||||
|
||||
#[post("/pay_u/notify")]
|
||||
#[post("/payment/notify")]
|
||||
async fn handle_notification(
|
||||
Json(notify): Json<PaymentNotification>,
|
||||
payment: Data<Addr<PaymentManager>>,
|
||||
|
Loading…
Reference in New Issue
Block a user