Add many additional create order fields
This commit is contained in:
parent
9d6534c2a2
commit
9658abe3b8
@ -9,6 +9,8 @@ use sqlx::PgPool;
|
|||||||
pub use stocks::*;
|
pub use stocks::*;
|
||||||
pub use tokens::*;
|
pub use tokens::*;
|
||||||
|
|
||||||
|
use crate::config::SharedAppConfig;
|
||||||
|
|
||||||
pub mod account_orders;
|
pub mod account_orders;
|
||||||
pub mod accounts;
|
pub mod accounts;
|
||||||
pub mod order_items;
|
pub mod order_items;
|
||||||
@ -72,8 +74,9 @@ impl Clone for Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Database {
|
impl Database {
|
||||||
pub(crate) async fn build(url: &str) -> Result<Self> {
|
pub(crate) async fn build(config: SharedAppConfig) -> Result<Self> {
|
||||||
let pool = sqlx::PgPool::connect(url).await.map_err(Error::Connect)?;
|
let url = config.lock().database().url();
|
||||||
|
let pool = sqlx::PgPool::connect(&url).await.map_err(Error::Connect)?;
|
||||||
Ok(Database { pool })
|
Ok(Database { pool })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::config::SharedAppConfig;
|
||||||
use crate::Email;
|
use crate::Email;
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
@ -29,7 +30,7 @@ pub struct SendState {
|
|||||||
pub struct EmailManager(Arc<Inner>);
|
pub struct EmailManager(Arc<Inner>);
|
||||||
|
|
||||||
pub(crate) struct Inner {
|
pub(crate) struct Inner {
|
||||||
from: Email,
|
config: SharedAppConfig,
|
||||||
send_grid: sendgrid::SGClient,
|
send_grid: sendgrid::SGClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,14 +39,10 @@ impl actix::Actor for EmailManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EmailManager {
|
impl EmailManager {
|
||||||
pub fn build() -> Result<Self> {
|
pub fn build(config: SharedAppConfig) -> Result<Self> {
|
||||||
let from = std::env::var("SMTP_FROM").expect("Missing SMTP_FROM variable");
|
|
||||||
|
|
||||||
Ok(Self(Arc::new(Inner {
|
Ok(Self(Arc::new(Inner {
|
||||||
from: Email::from(from),
|
config: config.clone(),
|
||||||
send_grid: sendgrid::SGClient::new(
|
send_grid: sendgrid::SGClient::new(config.lock().mail().sendgrid_secret()),
|
||||||
std::env::var("SENDGRID_SECRET").expect("Missing SENDGRID_SECRET variable"),
|
|
||||||
),
|
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,13 +56,12 @@ pub struct TestMail {
|
|||||||
mail_async_handler!(TestMail, test_mail, SendState);
|
mail_async_handler!(TestMail, test_mail, SendState);
|
||||||
|
|
||||||
pub(crate) async fn test_mail(msg: TestMail, inner: Arc<Inner>) -> Result<SendState> {
|
pub(crate) async fn test_mail(msg: TestMail, inner: Arc<Inner>) -> Result<SendState> {
|
||||||
let from: &str = &*inner.from;
|
|
||||||
let status = inner
|
let status = inner
|
||||||
.send_grid
|
.send_grid
|
||||||
.send(
|
.send(
|
||||||
sendgrid::Mail::new()
|
sendgrid::Mail::new()
|
||||||
.add_to((msg.receiver.as_str(), "User").into())
|
.add_to((msg.receiver.as_str(), "User").into())
|
||||||
.add_from(from)
|
.add_from(&inner.config.lock().mail().smtp_from())
|
||||||
.add_subject("Test e-mail")
|
.add_subject("Test e-mail")
|
||||||
.add_html("<h1>Test e-mail</h1>")
|
.add_html("<h1>Test e-mail</h1>")
|
||||||
.build(),
|
.build(),
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use actix::Message;
|
use actix::Message;
|
||||||
|
|
||||||
|
use crate::config::SharedAppConfig;
|
||||||
use crate::database::{self, SharedDatabase};
|
use crate::database::{self, SharedDatabase};
|
||||||
use crate::model::{
|
use crate::model::{
|
||||||
AccountId, AccountOrder, OrderStatus, ShoppingCart, ShoppingCartId, ShoppingCartItem,
|
AccountId, AccountOrder, OrderStatus, ShoppingCart, ShoppingCartId, ShoppingCartItem,
|
||||||
@ -14,7 +15,8 @@ macro_rules! order_async_handler {
|
|||||||
fn handle(&mut self, msg: $msg, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: $msg, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use actix::WrapFuture;
|
use actix::WrapFuture;
|
||||||
let db = self.db.clone();
|
let db = self.db.clone();
|
||||||
Box::pin(async { $async(msg, db).await }.into_actor(self))
|
let config = self.config.clone();
|
||||||
|
Box::pin(async { $async(msg, db, config).await }.into_actor(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -34,6 +36,7 @@ pub type Result<T> = std::result::Result<T, Error>;
|
|||||||
|
|
||||||
pub struct OrderManager {
|
pub struct OrderManager {
|
||||||
db: SharedDatabase,
|
db: SharedDatabase,
|
||||||
|
config: SharedAppConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl actix::Actor for OrderManager {
|
impl actix::Actor for OrderManager {
|
||||||
@ -41,8 +44,8 @@ impl actix::Actor for OrderManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl OrderManager {
|
impl OrderManager {
|
||||||
pub fn new(db: SharedDatabase) -> Self {
|
pub fn new(config: SharedAppConfig, db: SharedDatabase) -> Self {
|
||||||
Self { db }
|
Self { db, config }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +58,11 @@ pub struct CreateOrder {
|
|||||||
|
|
||||||
order_async_handler!(CreateOrder, create_order, AccountOrder);
|
order_async_handler!(CreateOrder, create_order, AccountOrder);
|
||||||
|
|
||||||
pub(crate) async fn create_order(msg: CreateOrder, db: SharedDatabase) -> Result<AccountOrder> {
|
pub(crate) async fn create_order(
|
||||||
|
msg: CreateOrder,
|
||||||
|
db: SharedDatabase,
|
||||||
|
_config: SharedAppConfig,
|
||||||
|
) -> Result<AccountOrder> {
|
||||||
let cart: ShoppingCart = match db
|
let cart: ShoppingCart = match db
|
||||||
.send(database::FindShoppingCart {
|
.send(database::FindShoppingCart {
|
||||||
id: msg.shopping_cart_id,
|
id: msg.shopping_cart_id,
|
||||||
|
@ -2,8 +2,9 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use actix::Addr;
|
use actix::Addr;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use pay_u::{MerchantPosId, OrderCreateRequest};
|
use pay_u::OrderCreateRequest;
|
||||||
|
|
||||||
|
use crate::config::SharedAppConfig;
|
||||||
use crate::database;
|
use crate::database;
|
||||||
use crate::database::Database;
|
use crate::database::Database;
|
||||||
use crate::model::{AccountId, Price, ProductId, Quantity, QuantityUnit, ShoppingCartId};
|
use crate::model::{AccountId, Price, ProductId, Quantity, QuantityUnit, ShoppingCartId};
|
||||||
@ -43,18 +44,12 @@ pub struct PaymentManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl PaymentManager {
|
impl PaymentManager {
|
||||||
pub async fn build<ClientId, ClientSecret>(
|
pub async fn build(config: SharedAppConfig, db: Addr<Database>) -> Result<Self> {
|
||||||
client_id: ClientId,
|
let mut client = pay_u::Client::new(
|
||||||
client_secret: ClientSecret,
|
config.lock().payment().payu_client_id(),
|
||||||
merchant_pos_id: MerchantPosId,
|
config.lock().payment().payu_client_secret(),
|
||||||
db: Addr<Database>,
|
config.lock().payment().payu_client_merchant_id(),
|
||||||
) -> Result<Self>
|
);
|
||||||
where
|
|
||||||
ClientId: Into<pay_u::ClientId>,
|
|
||||||
ClientSecret: Into<pay_u::ClientSecret>,
|
|
||||||
{
|
|
||||||
let mut client =
|
|
||||||
pay_u::Client::new(client_id.into(), client_secret.into(), merchant_pos_id);
|
|
||||||
client.authorize().await?;
|
client.authorize().await?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
client: Arc::new(Mutex::new(client)),
|
client: Arc::new(Mutex::new(client)),
|
||||||
@ -154,14 +149,16 @@ pub(crate) async fn request_payment(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut client = client.lock();
|
let order = {
|
||||||
let order = client
|
client
|
||||||
.create_order(
|
.lock()
|
||||||
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?;
|
)
|
||||||
|
.await?
|
||||||
|
};
|
||||||
Ok(order.order_id)
|
Ok(order.order_id)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use actix::{Addr, Message};
|
use actix::{Addr, Message};
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
@ -8,6 +7,7 @@ use hmac::digest::KeyInit;
|
|||||||
use hmac::Hmac;
|
use hmac::Hmac;
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
|
|
||||||
|
use crate::config::SharedAppConfig;
|
||||||
use crate::database::{Database, TokenByJti};
|
use crate::database::{Database, TokenByJti};
|
||||||
use crate::model::{AccountId, Audience, Token, TokenString};
|
use crate::model::{AccountId, Audience, Token, TokenString};
|
||||||
use crate::{database, Role};
|
use crate::{database, Role};
|
||||||
@ -21,8 +21,8 @@ macro_rules! token_async_handler {
|
|||||||
fn handle(&mut self, msg: $msg, _ctx: &mut Self::Context) -> Self::Result {
|
fn handle(&mut self, msg: $msg, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
use actix::WrapFuture;
|
use actix::WrapFuture;
|
||||||
let db = self.db.clone();
|
let db = self.db.clone();
|
||||||
let secret = self.secret.clone();
|
let config = self.config.clone();
|
||||||
Box::pin(async { $async(msg, db, secret).await }.into_actor(self))
|
Box::pin(async { $async(msg, db, config).await }.into_actor(self))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -68,7 +68,7 @@ pub type Result<T> = std::result::Result<T, Error>;
|
|||||||
|
|
||||||
pub struct TokenManager {
|
pub struct TokenManager {
|
||||||
db: Addr<Database>,
|
db: Addr<Database>,
|
||||||
secret: Arc<String>,
|
config: SharedAppConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl actix::Actor for TokenManager {
|
impl actix::Actor for TokenManager {
|
||||||
@ -76,9 +76,8 @@ impl actix::Actor for TokenManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TokenManager {
|
impl TokenManager {
|
||||||
pub fn new(db: Addr<Database>) -> Self {
|
pub fn new(config: SharedAppConfig, db: Addr<Database>) -> Self {
|
||||||
let secret = Arc::new(std::env::var("JWT_SECRET").expect("JWT_SECRET is required"));
|
Self { db, config }
|
||||||
Self { db, secret }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +95,7 @@ token_async_handler!(CreateToken, create_token, (Token, TokenString));
|
|||||||
pub(crate) async fn create_token(
|
pub(crate) async fn create_token(
|
||||||
msg: CreateToken,
|
msg: CreateToken,
|
||||||
db: Addr<Database>,
|
db: Addr<Database>,
|
||||||
secret: Arc<String>,
|
config: SharedAppConfig,
|
||||||
) -> Result<(Token, TokenString)> {
|
) -> Result<(Token, TokenString)> {
|
||||||
let CreateToken {
|
let CreateToken {
|
||||||
customer_id,
|
customer_id,
|
||||||
@ -129,6 +128,7 @@ pub(crate) async fn create_token(
|
|||||||
let token_string = {
|
let token_string = {
|
||||||
use jwt::SignWithKey;
|
use jwt::SignWithKey;
|
||||||
|
|
||||||
|
let secret = config.lock().web().jwt_secret();
|
||||||
let key: Hmac<Sha256> = build_key(secret)?;
|
let key: Hmac<Sha256> = build_key(secret)?;
|
||||||
let mut claims = BTreeMap::new();
|
let mut claims = BTreeMap::new();
|
||||||
|
|
||||||
@ -195,12 +195,13 @@ token_async_handler!(Validate, validate, (Token, bool));
|
|||||||
pub(crate) async fn validate(
|
pub(crate) async fn validate(
|
||||||
msg: Validate,
|
msg: Validate,
|
||||||
db: Addr<Database>,
|
db: Addr<Database>,
|
||||||
secret: Arc<String>,
|
config: SharedAppConfig,
|
||||||
) -> Result<(Token, bool)> {
|
) -> Result<(Token, bool)> {
|
||||||
use jwt::VerifyWithKey;
|
use jwt::VerifyWithKey;
|
||||||
|
|
||||||
log::info!("Validating token {:?}", msg.token);
|
log::info!("Validating token {:?}", msg.token);
|
||||||
|
|
||||||
|
let secret = config.lock().web().jwt_secret();
|
||||||
let key: Hmac<Sha256> = build_key(secret)?;
|
let key: Hmac<Sha256> = build_key(secret)?;
|
||||||
let claims: BTreeMap<String, String> = match msg.token.verify_with_key(&key) {
|
let claims: BTreeMap<String, String> = match msg.token.verify_with_key(&key) {
|
||||||
Ok(claims) => claims,
|
Ok(claims) => claims,
|
||||||
@ -260,7 +261,7 @@ pub(crate) async fn validate(
|
|||||||
Ok((token, true))
|
Ok((token, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_key(secret: Arc<String>) -> Result<Hmac<Sha256>> {
|
fn build_key(secret: String) -> Result<Hmac<Sha256>> {
|
||||||
match Hmac::new_from_slice(secret.as_bytes()) {
|
match Hmac::new_from_slice(secret.as_bytes()) {
|
||||||
Ok(key) => Ok(key),
|
Ok(key) => Ok(key),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -1,9 +1,36 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use parking_lot::Mutex;
|
||||||
|
use password_hash::SaltString;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
trait Example: Sized {
|
trait Example: Sized {
|
||||||
fn example() -> Self;
|
fn example() -> Self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SharedAppConfig(Arc<Mutex<AppConfig>>);
|
||||||
|
|
||||||
|
impl SharedAppConfig {
|
||||||
|
fn new(app_config: AppConfig) -> Self {
|
||||||
|
Self(Arc::new(Mutex::new(app_config)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for SharedAppConfig {
|
||||||
|
type Target = Arc<Mutex<AppConfig>>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::DerefMut for SharedAppConfig {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default)]
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
pub struct PaymentConfig {
|
pub struct PaymentConfig {
|
||||||
payu_client_id: Option<pay_u::ClientId>,
|
payu_client_id: Option<pay_u::ClientId>,
|
||||||
@ -32,10 +59,9 @@ impl PaymentConfig {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.cloned()
|
.cloned()
|
||||||
.or_else(|| std::env::var("PAYU_CLIENT_ID").ok().map(pay_u::ClientId))
|
.or_else(|| std::env::var("PAYU_CLIENT_ID").ok().map(pay_u::ClientId))
|
||||||
.unwrap_or_else(|| {
|
.expect("payment config payu_client_id nor PAYU_CLIENT_ID env was given")
|
||||||
panic!("payment config payu_client_id nor PAYU_CLIENT_ID env was given")
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn payu_client_secret(&self) -> pay_u::ClientSecret {
|
pub fn payu_client_secret(&self) -> pay_u::ClientSecret {
|
||||||
self.payu_client_secret
|
self.payu_client_secret
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@ -45,14 +71,20 @@ impl PaymentConfig {
|
|||||||
.ok()
|
.ok()
|
||||||
.map(pay_u::ClientSecret)
|
.map(pay_u::ClientSecret)
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| {
|
.expect("payment config payu_client_secret nor PAYU_CLIENT_SECRET env was given")
|
||||||
panic!("payment config payu_client_secret nor PAYU_CLIENT_SECRET env was given")
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn payu_client_merchant_id(&self) -> pay_u::MerchantPosId {
|
pub fn payu_client_merchant_id(&self) -> pay_u::MerchantPosId {
|
||||||
self.payu_client_merchant_id
|
self.payu_client_merchant_id
|
||||||
.or_else(|| std::env::var("PAYU_CLIENT_MERCHANT_ID").ok().and_then(|s| s.parse::<i32>().ok()).map(pay_u::MerchantPosId))
|
.or_else(|| {
|
||||||
.unwrap_or_else(|| panic!("payment config payu_client_merchant_id nor PAYU_CLIENT_MERCHANT_ID env was given"))
|
std::env::var("PAYU_CLIENT_MERCHANT_ID")
|
||||||
|
.ok()
|
||||||
|
.and_then(|s| s.parse::<i32>().ok())
|
||||||
|
.map(pay_u::MerchantPosId)
|
||||||
|
})
|
||||||
|
.expect(
|
||||||
|
"payment config payu_client_merchant_id nor PAYU_CLIENT_MERCHANT_ID env was given",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,35 +122,48 @@ impl WebConfig {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.cloned()
|
.cloned()
|
||||||
.or_else(|| std::env::var("WEB_HOST").ok())
|
.or_else(|| std::env::var("WEB_HOST").ok())
|
||||||
.unwrap_or_else(|| panic!("web host config nor WEB_HOST env was not given"))
|
.expect("web host config nor WEB_HOST env was not given")
|
||||||
}
|
}
|
||||||
pub fn pass_salt(&self) -> String {
|
|
||||||
self.pass_salt
|
pub fn pass_salt(&self) -> SaltString {
|
||||||
.as_ref()
|
SaltString::new(
|
||||||
.cloned()
|
&self
|
||||||
.or_else(|| std::env::var("PASS_SALT").ok())
|
.pass_salt
|
||||||
.unwrap_or_else(|| panic!("Web config pass_salt nor PASS_SALT env was given"))
|
.as_ref()
|
||||||
|
.cloned()
|
||||||
|
.or_else(|| std::env::var("PASS_SALT").ok())
|
||||||
|
.expect("Web config pass_salt nor PASS_SALT env was given"),
|
||||||
|
)
|
||||||
|
.expect("Invalid password hash")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_secret(&self) -> String {
|
pub fn session_secret(&self) -> String {
|
||||||
self.session_secret
|
self.session_secret
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.cloned()
|
.cloned()
|
||||||
.or_else(|| std::env::var("SESSION_SECRET").ok())
|
.or_else(|| std::env::var("SESSION_SECRET").ok())
|
||||||
.unwrap_or_else(|| panic!("Web config session_secret nor SESSION_SECRET env was given"))
|
.expect("Web config session_secret nor SESSION_SECRET env was given")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn jwt_secret(&self) -> String {
|
pub fn jwt_secret(&self) -> String {
|
||||||
self.jwt_secret
|
self.jwt_secret
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.cloned()
|
.cloned()
|
||||||
.or_else(|| std::env::var("JWT_SECRET").ok())
|
.or_else(|| std::env::var("JWT_SECRET").ok())
|
||||||
.unwrap_or_else(|| panic!("Web config jwt_secret nor JWT_SECRET env was given"))
|
.expect("Web config jwt_secret nor JWT_SECRET env was given")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bind(&self) -> Option<String> {
|
pub fn bind(&self) -> Option<String> {
|
||||||
self.bind
|
self.bind
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.cloned()
|
.cloned()
|
||||||
.or_else(|| std::env::var("BAZZAR_BIND").ok())
|
.or_else(|| std::env::var("BAZZAR_BIND").ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_bind<S: Into<String>>(&mut self, bind: S) {
|
||||||
|
self.bind = Some(bind.into());
|
||||||
|
}
|
||||||
|
|
||||||
pub fn port(&self) -> Option<u16> {
|
pub fn port(&self) -> Option<u16> {
|
||||||
self.port.as_ref().copied().or_else(|| {
|
self.port.as_ref().copied().or_else(|| {
|
||||||
std::env::var("BAZZAR_PORT")
|
std::env::var("BAZZAR_PORT")
|
||||||
@ -126,6 +171,10 @@ impl WebConfig {
|
|||||||
.and_then(|s| s.parse::<u16>().ok())
|
.and_then(|s| s.parse::<u16>().ok())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_port(&mut self, port: u16) {
|
||||||
|
self.port = Some(port);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default)]
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
@ -157,25 +206,23 @@ impl MailConfig {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.cloned()
|
.cloned()
|
||||||
.or_else(|| std::env::var("SENDGRID_SECRET").ok())
|
.or_else(|| std::env::var("SENDGRID_SECRET").ok())
|
||||||
.unwrap_or_else(|| {
|
.expect("Mail sendgrid_secret config nor SENDGRID_SECRET env was given")
|
||||||
panic!("Mail sendgrid_secret config nor SENDGRID_SECRET env was given")
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sendgrid_api_key(&self) -> String {
|
pub fn sendgrid_api_key(&self) -> String {
|
||||||
self.sendgrid_api_key
|
self.sendgrid_api_key
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.cloned()
|
.cloned()
|
||||||
.or_else(|| std::env::var("SENDGRID_API_KEY").ok())
|
.or_else(|| std::env::var("SENDGRID_API_KEY").ok())
|
||||||
.unwrap_or_else(|| {
|
.expect("Mail sendgrid_api_key config nor SENDGRID_API_KEY env was given")
|
||||||
panic!("Mail sendgrid_api_key config nor SENDGRID_API_KEY env was given")
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn smtp_from(&self) -> String {
|
pub fn smtp_from(&self) -> String {
|
||||||
self.smtp_from
|
self.smtp_from
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.cloned()
|
.cloned()
|
||||||
.or_else(|| std::env::var("SMTP_FROM").ok())
|
.or_else(|| std::env::var("SMTP_FROM").ok())
|
||||||
.unwrap_or_else(|| panic!("Mail smtp_from config nor SMTP_FROM env was given"))
|
.expect("Mail smtp_from config nor SMTP_FROM env was given")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,7 +245,11 @@ impl DatabaseConfig {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.cloned()
|
.cloned()
|
||||||
.or_else(|| std::env::var("DATABASE_URL").ok())
|
.or_else(|| std::env::var("DATABASE_URL").ok())
|
||||||
.unwrap_or_else(|| panic!("Database url nor DATABASE_URL env was given"))
|
.expect("Database url nor DATABASE_URL env was given")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_url<S: Into<String>>(&mut self, url: S) {
|
||||||
|
self.url = Some(url.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,15 +279,34 @@ impl AppConfig {
|
|||||||
pub fn payment(&self) -> &PaymentConfig {
|
pub fn payment(&self) -> &PaymentConfig {
|
||||||
&self.payment
|
&self.payment
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn web(&self) -> &WebConfig {
|
pub fn web(&self) -> &WebConfig {
|
||||||
&self.web
|
&self.web
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mail(&self) -> &MailConfig {
|
pub fn mail(&self) -> &MailConfig {
|
||||||
&self.mail
|
&self.mail
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn database(&self) -> &DatabaseConfig {
|
pub fn database(&self) -> &DatabaseConfig {
|
||||||
&self.database
|
&self.database
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn payment_mut(&mut self) -> &mut PaymentConfig {
|
||||||
|
&mut self.payment
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn web_mut(&mut self) -> &mut WebConfig {
|
||||||
|
&mut self.web
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mail_mut(&mut self) -> &mut MailConfig {
|
||||||
|
&mut self.mail
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn database_mut(&mut self) -> &mut DatabaseConfig {
|
||||||
|
&mut self.database
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for AppConfig {
|
impl Default for AppConfig {
|
||||||
@ -251,13 +321,13 @@ impl Default for AppConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(config_path: &str) -> AppConfig {
|
pub fn load(config_path: &str) -> SharedAppConfig {
|
||||||
match std::fs::read_to_string(config_path) {
|
match std::fs::read_to_string(config_path) {
|
||||||
Ok(c) => toml::from_str(&c).unwrap(),
|
Ok(c) => SharedAppConfig::new(toml::from_str(&c).unwrap()),
|
||||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||||
let config = AppConfig::example();
|
let config = AppConfig::example();
|
||||||
std::fs::write(config_path, toml::to_string_pretty(&config).unwrap()).unwrap();
|
std::fs::write(config_path, toml::to_string_pretty(&config).unwrap()).unwrap();
|
||||||
config
|
SharedAppConfig::new(config)
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
|
198
api/src/main.rs
198
api/src/main.rs
@ -1,5 +1,4 @@
|
|||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use actix::Actor;
|
use actix::Actor;
|
||||||
use actix_session::storage::RedisActorSessionStore;
|
use actix_session::storage::RedisActorSessionStore;
|
||||||
@ -8,36 +7,30 @@ 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};
|
||||||
use gumdrop::Options;
|
|
||||||
use jemallocator::Jemalloc;
|
use jemallocator::Jemalloc;
|
||||||
|
use opts::{
|
||||||
|
Command, CreateAccountCmd, CreateAccountOpts, GenerateHashOpts, MigrateOpts, Opts, ServerOpts,
|
||||||
|
TestMailerOpts,
|
||||||
|
};
|
||||||
use password_hash::SaltString;
|
use password_hash::SaltString;
|
||||||
use pay_u::MerchantPosId;
|
|
||||||
use validator::{validate_email, validate_length};
|
use validator::{validate_email, validate_length};
|
||||||
|
|
||||||
use crate::actors::{database, email_manager, order_manager, payment_manager, token_manager};
|
use crate::actors::{database, email_manager, order_manager, payment_manager, token_manager};
|
||||||
use crate::email_manager::TestMail;
|
use crate::email_manager::TestMail;
|
||||||
use crate::logic::encrypt_password;
|
use crate::logic::encrypt_password;
|
||||||
use crate::model::{Email, Login, PassHash, Password, Role};
|
use crate::model::{Email, Login, PassHash, Password, Role};
|
||||||
|
use crate::opts::UpdateConfig;
|
||||||
|
|
||||||
pub mod actors;
|
pub mod actors;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod logic;
|
pub mod logic;
|
||||||
pub mod model;
|
pub mod model;
|
||||||
|
mod opts;
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
|
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
static GLOBAL: Jemalloc = Jemalloc;
|
static GLOBAL: Jemalloc = Jemalloc;
|
||||||
|
|
||||||
trait ResolveDbUrl {
|
|
||||||
fn own_db_url(&self) -> Option<String>;
|
|
||||||
|
|
||||||
fn db_url(&self) -> String {
|
|
||||||
self.own_db_url()
|
|
||||||
.or_else(|| std::env::var("DATABASE_URL").ok())
|
|
||||||
.unwrap_or_else(|| String::from("postgres://postgres@localhost/bazzar"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Failed to boot. {0:?}")]
|
#[error("Failed to boot. {0:?}")]
|
||||||
@ -52,127 +45,6 @@ pub enum Error {
|
|||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
#[derive(Options, Debug)]
|
|
||||||
struct Opts {
|
|
||||||
help: bool,
|
|
||||||
#[options(command)]
|
|
||||||
cmd: Option<Command>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Options, Debug)]
|
|
||||||
enum Command {
|
|
||||||
#[options(help = "Run server")]
|
|
||||||
Server(ServerOpts),
|
|
||||||
#[options(help = "Migrate database")]
|
|
||||||
Migrate(MigrateOpts),
|
|
||||||
#[options(help = "Generate new salt for passwords")]
|
|
||||||
GenerateHash(GenerateHashOpts),
|
|
||||||
#[options(help = "Create new account")]
|
|
||||||
CreateAccount(CreateAccountOpts),
|
|
||||||
#[options(help = "Check mailer config")]
|
|
||||||
TestMailer(TestMailerOpts),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Command {
|
|
||||||
fn default() -> Self {
|
|
||||||
Command::Server(ServerOpts::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Options, Debug)]
|
|
||||||
struct GenerateHashOpts {
|
|
||||||
help: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Options, Debug)]
|
|
||||||
struct ServerOpts {
|
|
||||||
help: bool,
|
|
||||||
bind: String,
|
|
||||||
port: u16,
|
|
||||||
db_url: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ServerOpts {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
help: false,
|
|
||||||
bind: "0.0.0.0".to_string(),
|
|
||||||
port: 8080,
|
|
||||||
db_url: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResolveDbUrl for ServerOpts {
|
|
||||||
fn own_db_url(&self) -> Option<String> {
|
|
||||||
self.db_url.as_deref().map(String::from)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Options, Debug)]
|
|
||||||
pub struct TestMailerOpts {
|
|
||||||
help: bool,
|
|
||||||
#[options(help = "E-mail receiver")]
|
|
||||||
receiver: Option<Email>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Options, Debug)]
|
|
||||||
struct MigrateOpts {
|
|
||||||
help: bool,
|
|
||||||
db_url: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResolveDbUrl for MigrateOpts {
|
|
||||||
fn own_db_url(&self) -> Option<String> {
|
|
||||||
self.db_url.as_deref().map(String::from)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Options)]
|
|
||||||
struct CreateAccountOpts {
|
|
||||||
help: bool,
|
|
||||||
#[options(command)]
|
|
||||||
cmd: Option<CreateAccountCmd>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Options)]
|
|
||||||
enum CreateAccountCmd {
|
|
||||||
Admin(CreateAccountDefinition),
|
|
||||||
User(CreateAccountDefinition),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Options)]
|
|
||||||
struct CreateAccountDefinition {
|
|
||||||
help: bool,
|
|
||||||
#[options(free)]
|
|
||||||
login: String,
|
|
||||||
#[options(free)]
|
|
||||||
email: String,
|
|
||||||
#[options(free)]
|
|
||||||
pass_file: Option<String>,
|
|
||||||
#[options(help = "Database url, it will also look for DATABASE_URL env")]
|
|
||||||
db_url: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ResolveDbUrl for CreateAccountDefinition {
|
|
||||||
fn own_db_url(&self) -> Option<String> {
|
|
||||||
self.db_url.as_deref().map(String::from)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Config {
|
|
||||||
pass_salt: SaltString,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
fn load() -> Self {
|
|
||||||
let pass_salt =
|
|
||||||
SaltString::new(&std::env::var("PASS_SALT").expect("PASS_SALT is required"))
|
|
||||||
.expect("Invalid password salt");
|
|
||||||
Self { pass_salt }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn server(opts: ServerOpts) -> Result<()> {
|
async fn server(opts: ServerOpts) -> Result<()> {
|
||||||
let secret_key = {
|
let secret_key = {
|
||||||
let key_secret = std::env::var("SESSION_SECRET")
|
let key_secret = std::env::var("SESSION_SECRET")
|
||||||
@ -181,28 +53,25 @@ async fn server(opts: ServerOpts) -> Result<()> {
|
|||||||
};
|
};
|
||||||
let redis_connection_string = "127.0.0.1:6379";
|
let redis_connection_string = "127.0.0.1:6379";
|
||||||
|
|
||||||
let app_config = crate::config::load("./bazzar.toml");
|
let app_config = config::load("./bazzar.toml");
|
||||||
|
{
|
||||||
|
opts.update_config(&mut *app_config.lock());
|
||||||
|
}
|
||||||
|
|
||||||
let config = Arc::new(Config::load());
|
let db = database::Database::build(app_config.clone()).await?.start();
|
||||||
let db = database::Database::build(&opts.db_url()).await?.start();
|
let token_manager = token_manager::TokenManager::new(app_config.clone(), db.clone()).start();
|
||||||
let token_manager = token_manager::TokenManager::new(db.clone()).start();
|
let order_manager = order_manager::OrderManager::new(app_config.clone(), db.clone()).start();
|
||||||
let order_manager = order_manager::OrderManager::new(db.clone()).start();
|
let payment_manager = payment_manager::PaymentManager::build(app_config.clone(), db.clone())
|
||||||
let payment_manager = {
|
.await
|
||||||
let client_id = std::env::var("PAYU_CLIENT_ID").expect("Missing PAYU_CLIENT_ID env");
|
.expect("Failed to start payment manager")
|
||||||
let client_secret =
|
.start();
|
||||||
std::env::var("PAYU_CLIENT_SECRET").expect("Missing PAYU_CLIENT_SECRET env");
|
let addr = (
|
||||||
let merchant_id = std::env::var("PAYU_CLIENT_MERCHANT_ID")
|
app_config.lock().web().bind().unwrap_or(opts.bind),
|
||||||
.expect("Missing PAYU_CLIENT_MERCHANT_ID env")
|
app_config.lock().web().port().unwrap_or(opts.port),
|
||||||
.parse::<i32>()
|
);
|
||||||
.map(MerchantPosId::from)
|
|
||||||
.expect("Variable PAYU_CLIENT_MERCHANT_ID must be number");
|
|
||||||
payment_manager::PaymentManager::build(client_id, client_secret, merchant_id, db.clone())
|
|
||||||
.await
|
|
||||||
.expect("Failed to start payment manager")
|
|
||||||
.start()
|
|
||||||
};
|
|
||||||
|
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
|
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::Compress::default())
|
||||||
@ -211,7 +80,7 @@ async fn server(opts: ServerOpts) -> Result<()> {
|
|||||||
RedisActorSessionStore::new(redis_connection_string),
|
RedisActorSessionStore::new(redis_connection_string),
|
||||||
secret_key.clone(),
|
secret_key.clone(),
|
||||||
))
|
))
|
||||||
.app_data(Data::new(config.clone()))
|
.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()))
|
||||||
.app_data(Data::new(order_manager.clone()))
|
.app_data(Data::new(order_manager.clone()))
|
||||||
@ -219,10 +88,7 @@ async fn server(opts: ServerOpts) -> Result<()> {
|
|||||||
.configure(routes::configure)
|
.configure(routes::configure)
|
||||||
// .default_service(web::to(HttpResponse::Ok))
|
// .default_service(web::to(HttpResponse::Ok))
|
||||||
})
|
})
|
||||||
.bind((
|
.bind(addr)
|
||||||
app_config.web().bind().unwrap_or(opts.bind),
|
|
||||||
app_config.web().port().unwrap_or(opts.port),
|
|
||||||
))
|
|
||||||
.map_err(Error::Boot)?
|
.map_err(Error::Boot)?
|
||||||
.run()
|
.run()
|
||||||
.await
|
.await
|
||||||
@ -232,7 +98,9 @@ async fn server(opts: ServerOpts) -> Result<()> {
|
|||||||
async fn migrate(opts: MigrateOpts) -> Result<()> {
|
async fn migrate(opts: MigrateOpts) -> Result<()> {
|
||||||
use sqlx::migrate::MigrateError;
|
use sqlx::migrate::MigrateError;
|
||||||
|
|
||||||
let db = database::Database::build(&opts.db_url()).await?;
|
let config = config::load("./bazzar.toml");
|
||||||
|
opts.update_config(&mut *config.lock());
|
||||||
|
let db = database::Database::build(config).await?;
|
||||||
let res: std::result::Result<(), MigrateError> =
|
let res: std::result::Result<(), MigrateError> =
|
||||||
sqlx::migrate!("../db/migrate").run(db.pool()).await;
|
sqlx::migrate!("../db/migrate").run(db.pool()).await;
|
||||||
match res {
|
match res {
|
||||||
@ -262,7 +130,9 @@ async fn create_account(opts: CreateAccountOpts) -> Result<()> {
|
|||||||
if !validate_length(&opts.login, Some(4), Some(100), None) {
|
if !validate_length(&opts.login, Some(4), Some(100), None) {
|
||||||
panic!("Login must have at least 4 characters and no more than 100");
|
panic!("Login must have at least 4 characters and no more than 100");
|
||||||
}
|
}
|
||||||
let db = database::Database::build(&opts.db_url()).await?.start();
|
let config = config::load("./bazzar.toml");
|
||||||
|
opts.update_config(&mut *config.lock());
|
||||||
|
let db = database::Database::build(config.clone()).await?.start();
|
||||||
let pass = match opts.pass_file {
|
let pass = match opts.pass_file {
|
||||||
Some(path) => std::fs::read_to_string(path).map_err(Error::PassFile)?,
|
Some(path) => std::fs::read_to_string(path).map_err(Error::PassFile)?,
|
||||||
None => {
|
None => {
|
||||||
@ -287,8 +157,7 @@ async fn create_account(opts: CreateAccountOpts) -> Result<()> {
|
|||||||
if pass.trim().is_empty() {
|
if pass.trim().is_empty() {
|
||||||
panic!("Password cannot be empty!");
|
panic!("Password cannot be empty!");
|
||||||
}
|
}
|
||||||
let config = Config::load();
|
let hash = encrypt_password(&Password::from(pass), &config.lock().web().pass_salt()).unwrap();
|
||||||
let hash = encrypt_password(&Password::from(pass), &config.pass_salt).unwrap();
|
|
||||||
|
|
||||||
db.send(database::CreateAccount {
|
db.send(database::CreateAccount {
|
||||||
email: Email::from(opts.email),
|
email: Email::from(opts.email),
|
||||||
@ -303,7 +172,10 @@ async fn create_account(opts: CreateAccountOpts) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn test_mailer(opts: TestMailerOpts) -> Result<()> {
|
async fn test_mailer(opts: TestMailerOpts) -> Result<()> {
|
||||||
let manager = email_manager::EmailManager::build()
|
let config = config::load("./bazzar.toml");
|
||||||
|
opts.update_config(&mut *config.lock());
|
||||||
|
|
||||||
|
let manager = email_manager::EmailManager::build(config)
|
||||||
.expect("Invalid email manager config")
|
.expect("Invalid email manager config")
|
||||||
.start();
|
.start();
|
||||||
if manager
|
if manager
|
||||||
|
222
api/src/opts.rs
Normal file
222
api/src/opts.rs
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
use gumdrop::Options;
|
||||||
|
|
||||||
|
use crate::config::AppConfig;
|
||||||
|
use crate::model::Email;
|
||||||
|
|
||||||
|
pub trait UpdateConfig {
|
||||||
|
fn update_config(&self, config: &mut AppConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ResolveDbUrl {
|
||||||
|
fn own_db_url(&self) -> Option<String>;
|
||||||
|
|
||||||
|
fn db_url(&self) -> String {
|
||||||
|
self.own_db_url()
|
||||||
|
.or_else(|| std::env::var("DATABASE_URL").ok())
|
||||||
|
.unwrap_or_else(|| String::from("postgres://postgres@localhost/bazzar"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Options, Debug)]
|
||||||
|
pub struct Opts {
|
||||||
|
pub help: bool,
|
||||||
|
#[options(command)]
|
||||||
|
pub cmd: Option<Command>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateConfig for Opts {
|
||||||
|
fn update_config(&self, config: &mut AppConfig) {
|
||||||
|
match &self.cmd {
|
||||||
|
None => {}
|
||||||
|
Some(cmd) => {
|
||||||
|
cmd.update_config(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Options, Debug)]
|
||||||
|
pub enum Command {
|
||||||
|
#[options(help = "Run server")]
|
||||||
|
Server(ServerOpts),
|
||||||
|
#[options(help = "Migrate database")]
|
||||||
|
Migrate(MigrateOpts),
|
||||||
|
#[options(help = "Generate new salt for passwords")]
|
||||||
|
GenerateHash(GenerateHashOpts),
|
||||||
|
#[options(help = "Create new account")]
|
||||||
|
CreateAccount(CreateAccountOpts),
|
||||||
|
#[options(help = "Check mailer config")]
|
||||||
|
TestMailer(TestMailerOpts),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateConfig for Command {
|
||||||
|
fn update_config(&self, config: &mut AppConfig) {
|
||||||
|
match self {
|
||||||
|
Command::Server(opts) => {
|
||||||
|
opts.update_config(config);
|
||||||
|
}
|
||||||
|
Command::Migrate(opts) => {
|
||||||
|
opts.update_config(config);
|
||||||
|
}
|
||||||
|
Command::GenerateHash(opts) => {
|
||||||
|
opts.update_config(config);
|
||||||
|
}
|
||||||
|
Command::CreateAccount(opts) => {
|
||||||
|
opts.update_config(config);
|
||||||
|
}
|
||||||
|
Command::TestMailer(opts) => {
|
||||||
|
opts.update_config(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Command {
|
||||||
|
fn default() -> Self {
|
||||||
|
Command::Server(ServerOpts::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Options, Debug)]
|
||||||
|
pub struct GenerateHashOpts {
|
||||||
|
pub help: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateConfig for GenerateHashOpts {
|
||||||
|
fn update_config(&self, _config: &mut AppConfig) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Options, Debug)]
|
||||||
|
pub struct ServerOpts {
|
||||||
|
pub help: bool,
|
||||||
|
pub bind: String,
|
||||||
|
pub port: u16,
|
||||||
|
pub db_url: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ServerOpts {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
help: false,
|
||||||
|
bind: "0.0.0.0".to_string(),
|
||||||
|
port: 8080,
|
||||||
|
db_url: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateConfig for ServerOpts {
|
||||||
|
fn update_config(&self, config: &mut AppConfig) {
|
||||||
|
{
|
||||||
|
let web = config.web_mut();
|
||||||
|
if web.bind().is_none() {
|
||||||
|
web.set_bind(&self.bind);
|
||||||
|
}
|
||||||
|
if web.port().is_none() {
|
||||||
|
web.set_port(self.port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(url) = self.db_url.as_ref() {
|
||||||
|
config.database_mut().set_url(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResolveDbUrl for ServerOpts {
|
||||||
|
fn own_db_url(&self) -> Option<String> {
|
||||||
|
self.db_url.as_deref().map(String::from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Options, Debug)]
|
||||||
|
pub struct TestMailerOpts {
|
||||||
|
pub help: bool,
|
||||||
|
#[options(help = "E-mail receiver")]
|
||||||
|
pub receiver: Option<Email>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateConfig for TestMailerOpts {
|
||||||
|
fn update_config(&self, _config: &mut AppConfig) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Options, Debug)]
|
||||||
|
pub struct MigrateOpts {
|
||||||
|
pub help: bool,
|
||||||
|
pub db_url: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateConfig for MigrateOpts {
|
||||||
|
fn update_config(&self, config: &mut AppConfig) {
|
||||||
|
if let Some(url) = self.db_url.as_deref() {
|
||||||
|
config.database_mut().set_url(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResolveDbUrl for MigrateOpts {
|
||||||
|
fn own_db_url(&self) -> Option<String> {
|
||||||
|
self.db_url.as_deref().map(String::from)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Options)]
|
||||||
|
pub struct CreateAccountOpts {
|
||||||
|
pub help: bool,
|
||||||
|
#[options(command)]
|
||||||
|
pub cmd: Option<CreateAccountCmd>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateConfig for CreateAccountOpts {
|
||||||
|
fn update_config(&self, config: &mut AppConfig) {
|
||||||
|
match &self.cmd {
|
||||||
|
None => {}
|
||||||
|
Some(opts) => opts.update_config(config),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Options)]
|
||||||
|
pub enum CreateAccountCmd {
|
||||||
|
Admin(CreateAccountDefinition),
|
||||||
|
User(CreateAccountDefinition),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateConfig for CreateAccountCmd {
|
||||||
|
fn update_config(&self, config: &mut AppConfig) {
|
||||||
|
match &self {
|
||||||
|
CreateAccountCmd::Admin(opts) => {
|
||||||
|
opts.update_config(config);
|
||||||
|
}
|
||||||
|
CreateAccountCmd::User(opts) => {
|
||||||
|
opts.update_config(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Options)]
|
||||||
|
pub struct CreateAccountDefinition {
|
||||||
|
pub help: bool,
|
||||||
|
#[options(free)]
|
||||||
|
pub login: String,
|
||||||
|
#[options(free)]
|
||||||
|
pub email: String,
|
||||||
|
#[options(free)]
|
||||||
|
pub pass_file: Option<String>,
|
||||||
|
#[options(help = "Database url, it will also look for DATABASE_URL env")]
|
||||||
|
pub db_url: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateConfig for CreateAccountDefinition {
|
||||||
|
fn update_config(&self, config: &mut AppConfig) {
|
||||||
|
if let Some(url) = self.db_url.as_deref() {
|
||||||
|
config.database_mut().set_url(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResolveDbUrl for CreateAccountDefinition {
|
||||||
|
fn own_db_url(&self) -> Option<String> {
|
||||||
|
self.db_url.as_deref().map(String::from)
|
||||||
|
}
|
||||||
|
}
|
@ -1,18 +1,17 @@
|
|||||||
mod api_v1;
|
mod api_v1;
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use actix::Addr;
|
use actix::Addr;
|
||||||
use actix_session::Session;
|
use actix_session::Session;
|
||||||
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, HttpResponse};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::config::SharedAppConfig;
|
||||||
use crate::database::{AccountByIdentity, Database};
|
use crate::database::{AccountByIdentity, Database};
|
||||||
use crate::logic::encrypt_password;
|
use crate::logic::encrypt_password;
|
||||||
use crate::model::{Account, Email, Login, PassHash, Password, PasswordConfirmation, Role};
|
use crate::model::{Account, Email, Login, PassHash, Password, PasswordConfirmation, Role};
|
||||||
use crate::routes::{RequireLogin, Result};
|
use crate::routes::{RequireLogin, Result};
|
||||||
use crate::{database, model, routes, Config};
|
use crate::{database, model, routes};
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! admin_send_db {
|
macro_rules! admin_send_db {
|
||||||
@ -127,7 +126,7 @@ async fn register(
|
|||||||
session: Session,
|
session: Session,
|
||||||
Json(input): Json<RegisterInput>,
|
Json(input): Json<RegisterInput>,
|
||||||
db: Data<Addr<Database>>,
|
db: Data<Addr<Database>>,
|
||||||
config: Data<Arc<Config>>,
|
config: Data<SharedAppConfig>,
|
||||||
) -> Result<HttpResponse> {
|
) -> Result<HttpResponse> {
|
||||||
let mut response = RegisterResponse::default();
|
let mut response = RegisterResponse::default();
|
||||||
session.require_admin()?;
|
session.require_admin()?;
|
||||||
@ -136,7 +135,7 @@ async fn register(
|
|||||||
response.errors.push(RegisterError::PasswordDiffer);
|
response.errors.push(RegisterError::PasswordDiffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
let hash = match encrypt_password(&input.password, &config.pass_salt) {
|
let hash = match encrypt_password(&input.password, &config.lock().web().pass_salt()) {
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use actix::Addr;
|
use actix::Addr;
|
||||||
use actix_session::Session;
|
use actix_session::Session;
|
||||||
use actix_web::web::{Data, Json, ServiceConfig};
|
use actix_web::web::{Data, Json, ServiceConfig};
|
||||||
use actix_web::{get, patch, post, HttpResponse};
|
use actix_web::{get, patch, post, HttpResponse};
|
||||||
|
|
||||||
|
use crate::config::SharedAppConfig;
|
||||||
use crate::database::{self, Database};
|
use crate::database::{self, Database};
|
||||||
use crate::model::{AccountId, AccountState, PasswordConfirmation};
|
use crate::model::{AccountId, AccountState, PasswordConfirmation};
|
||||||
use crate::routes::admin::Error;
|
use crate::routes::admin::Error;
|
||||||
use crate::routes::RequireLogin;
|
use crate::routes::RequireLogin;
|
||||||
use crate::{
|
use crate::{admin_send_db, encrypt_password, routes, Email, Login, PassHash, Password, Role};
|
||||||
admin_send_db, encrypt_password, routes, Config, Email, Login, PassHash, Password, Role,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[get("/accounts")]
|
#[get("/accounts")]
|
||||||
pub async fn accounts(session: Session, db: Data<Addr<Database>>) -> routes::Result<HttpResponse> {
|
pub async fn accounts(session: Session, db: Data<Addr<Database>>) -> routes::Result<HttpResponse> {
|
||||||
@ -36,7 +33,7 @@ pub async fn update_account(
|
|||||||
session: Session,
|
session: Session,
|
||||||
db: Data<Addr<Database>>,
|
db: Data<Addr<Database>>,
|
||||||
Json(payload): Json<UpdateAccountInput>,
|
Json(payload): Json<UpdateAccountInput>,
|
||||||
config: Data<Arc<Config>>,
|
config: Data<SharedAppConfig>,
|
||||||
) -> routes::Result<HttpResponse> {
|
) -> routes::Result<HttpResponse> {
|
||||||
session.require_admin()?;
|
session.require_admin()?;
|
||||||
|
|
||||||
@ -48,7 +45,7 @@ pub async fn update_account(
|
|||||||
routes::admin::Error::DifferentPasswords,
|
routes::admin::Error::DifferentPasswords,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let hash = match encrypt_password(&p1, &config.pass_salt) {
|
let hash = match encrypt_password(&p1, &config.lock().web().pass_salt()) {
|
||||||
Ok(hash) => hash,
|
Ok(hash) => hash,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
@ -92,7 +89,7 @@ pub async fn create_account(
|
|||||||
session: Session,
|
session: Session,
|
||||||
db: Data<Addr<Database>>,
|
db: Data<Addr<Database>>,
|
||||||
Json(payload): Json<CreateAccountInput>,
|
Json(payload): Json<CreateAccountInput>,
|
||||||
config: Data<Arc<Config>>,
|
config: Data<SharedAppConfig>,
|
||||||
) -> routes::Result<HttpResponse> {
|
) -> routes::Result<HttpResponse> {
|
||||||
session.require_admin()?;
|
session.require_admin()?;
|
||||||
if payload.password != payload.password_confirmation {
|
if payload.password != payload.password_confirmation {
|
||||||
@ -100,7 +97,7 @@ pub async fn create_account(
|
|||||||
routes::admin::Error::DifferentPasswords,
|
routes::admin::Error::DifferentPasswords,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let hash = match encrypt_password(&payload.password, &config.pass_salt) {
|
let hash = match encrypt_password(&payload.password, &config.lock().web().pass_salt()) {
|
||||||
Ok(hash) => hash,
|
Ok(hash) => hash,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
log::error!("{e:?}");
|
log::error!("{e:?}");
|
||||||
|
204
pay_u/src/lib.rs
204
pay_u/src/lib.rs
@ -27,6 +27,8 @@ pub static SUCCESS: &str = "SUCCESS";
|
|||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Client is not authorized. No bearer token available")]
|
#[error("Client is not authorized. No bearer token available")]
|
||||||
NoToken,
|
NoToken,
|
||||||
|
#[error("Invalid customer ip. IP 0.0.0.0 is not acceptable")]
|
||||||
|
CustomerIp,
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
#[error("Total value is not sum of products price")]
|
#[error("Total value is not sum of products price")]
|
||||||
@ -316,9 +318,105 @@ impl Product {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// MultiUseCartToken
|
||||||
|
pub mod muct {
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum CardOnFile {
|
||||||
|
/// Payment initialized by the card owner who agreed to save card for
|
||||||
|
/// future use. You can expect full authentication (3D Secure
|
||||||
|
/// and/or CVV). If you want to use multi-use token (TOKC_)
|
||||||
|
/// later, you have to be confident, that first payment was
|
||||||
|
/// successful. Default value for single-use token (TOK_).
|
||||||
|
///
|
||||||
|
/// In case of plain card data payments you should retrieve transaction
|
||||||
|
/// data to obtain first TransactionId. It should be passed in
|
||||||
|
/// payMethods.payMethod.card section for transactions marked as
|
||||||
|
/// STANDARD, STANDARD_CARDHOLDER and STANDARD_MERCHANT;
|
||||||
|
/// STANDARD_CARDHOLDER - payment with already saved card,
|
||||||
|
/// initialized by the card owner. This transaction has
|
||||||
|
/// multi-use token (TOKC_). Depending of payment parameters
|
||||||
|
/// (e.g. high transaction amount) strong authentication can be
|
||||||
|
/// expected (3D Secure and/or CVV). Default value for multi-use token
|
||||||
|
/// (TOKC_);
|
||||||
|
First,
|
||||||
|
/// Payment with already saved card, initialized by the shop without the
|
||||||
|
/// card owner participation. This transaction has multi-use token
|
||||||
|
/// (TOKC_). By the definition, this payment type does not
|
||||||
|
/// require strong authentication. You cannot use it if FIRST
|
||||||
|
/// card-on-file payment failed.
|
||||||
|
StandardMerchant,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||||
|
pub enum Recurring {
|
||||||
|
/// Payment initialized by the card owner who agreed to save card for
|
||||||
|
/// future use in recurring plan. You can expect full authentication (3D
|
||||||
|
/// Secure and/or CVV). If you want to use multi-use token (TOKC_)
|
||||||
|
/// later, you have to be confident, that first recurring
|
||||||
|
/// payment was successful.
|
||||||
|
First,
|
||||||
|
/// Subsequent recurring payment (user is not present). This transaction
|
||||||
|
/// has multi use token (TOKC_). You cannot use it if FIRST recurring
|
||||||
|
/// payment failed.
|
||||||
|
Standard,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct MultiUseCartToken {
|
||||||
|
/// Information about party initializing order:
|
||||||
|
///
|
||||||
|
/// * `FIRST` - payment initialized by the card owner who agreed to save
|
||||||
|
/// card for future use. You can expect full authentication (3D Secure
|
||||||
|
/// and/or CVV). If you want to use multi-use token (TOKC_) later, you
|
||||||
|
/// have to be confident, that first payment was successful. Default
|
||||||
|
/// value for single-use token (TOK_).
|
||||||
|
///
|
||||||
|
/// In case of plain card data payments you should retrieve
|
||||||
|
/// transaction data to obtain first TransactionId. It should
|
||||||
|
/// be passed in payMethods.payMethod.card section for
|
||||||
|
/// transactions marked as STANDARD, STANDARD_CARDHOLDER and
|
||||||
|
/// STANDARD_MERCHANT; STANDARD_CARDHOLDER - payment with
|
||||||
|
/// already saved card, initialized by the card owner. This
|
||||||
|
/// transaction has multi-use token (TOKC_). Depending of payment
|
||||||
|
/// parameters (e.g. high transaction amount) strong authentication
|
||||||
|
/// can be expected (3D Secure and/or CVV). Default value for
|
||||||
|
/// multi-use token (TOKC_);
|
||||||
|
/// * `STANDARD_MERCHANT` - payment with already saved card, initialized
|
||||||
|
/// by the shop without the card owner participation. This transaction
|
||||||
|
/// has multi-use token (TOKC_). By the definition, this payment type
|
||||||
|
/// does not require strong authentication. You cannot use it if FIRST
|
||||||
|
/// card-on-file payment failed.
|
||||||
|
///
|
||||||
|
/// `cardOnFile` parameter cannot be used with recurring parameter.
|
||||||
|
pub card_on_file: CardOnFile,
|
||||||
|
/// Marks the order as recurring payment.
|
||||||
|
///
|
||||||
|
/// * `FIRST` - payment initialized by the card owner who agreed to save
|
||||||
|
/// card for future use in recurring plan. You can expect full
|
||||||
|
/// authentication (3D Secure and/or CVV). If you want to use
|
||||||
|
/// multi-use token (TOKC_) later, you have to be confident, that
|
||||||
|
/// first recurring payment was successful.
|
||||||
|
/// * `STANDARD` - subsequent recurring payment (user is not present).
|
||||||
|
/// This transaction has multi use token (TOKC_). You cannot use it if
|
||||||
|
/// FIRST recurring payment failed.
|
||||||
|
///
|
||||||
|
/// `recurring` parameter cannot be used with cardOnFile parameter.
|
||||||
|
pub recurring: Recurring,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct OrderCreateRequest {
|
pub struct OrderCreateRequest {
|
||||||
|
/// ID of an order used in merchant system. Order identifier assigned by the
|
||||||
|
/// merchant. It enables merchants to find a specific order in their system.
|
||||||
|
/// This value must be unique within a single POS.
|
||||||
|
ext_order_id: Option<String>,
|
||||||
/// URL to which web hook will be send. It's important to return 200 to all
|
/// URL to which web hook will be send. It's important to return 200 to all
|
||||||
/// notifications.
|
/// notifications.
|
||||||
///
|
///
|
||||||
@ -352,7 +450,22 @@ pub struct OrderCreateRequest {
|
|||||||
/// | 20| 72 hours |
|
/// | 20| 72 hours |
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
notify_url: Option<String>,
|
notify_url: Option<String>,
|
||||||
/// Customer client IP address
|
/// Address for redirecting the customer after payment is commenced. If the
|
||||||
|
/// payment has not been authorized, error=501 parameter will be added.
|
||||||
|
/// Please note that no decision regarding payment status should be made
|
||||||
|
/// depending on the presence or lack of this parameter (to get payment
|
||||||
|
/// status, wait for notification or retrieve order details).
|
||||||
|
///
|
||||||
|
/// IMPORTANT: the address must be compliant with the structure below:
|
||||||
|
/// <img src="https://developers.payu.com/images/continueUrlStructure_en.png" />
|
||||||
|
///
|
||||||
|
/// Please keep in mind:
|
||||||
|
/// * accepted schemas are http and https,
|
||||||
|
/// * such elements as port, path, query and fragment are optional,
|
||||||
|
/// * query values must be encoded.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
continue_url: Option<String>,
|
||||||
|
/// Payer’s IP address, e.g. 123.123.123.123. Note: 0.0.0.0 is not accepted.
|
||||||
customer_ip: String,
|
customer_ip: String,
|
||||||
/// Secret pos ip. This is connected to PayU account
|
/// Secret pos ip. This is connected to PayU account
|
||||||
#[serde(
|
#[serde(
|
||||||
@ -377,29 +490,106 @@ pub struct OrderCreateRequest {
|
|||||||
products: Vec<Product>,
|
products: Vec<Product>,
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing)]
|
||||||
order_create_date: Option<String>,
|
order_create_date: Option<String>,
|
||||||
|
/// Duration for the validity of an order (in seconds), during which time
|
||||||
|
/// payment must be made. Default value 86400.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
validity_time: Option<u16>,
|
||||||
|
/// Additional description of the order.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
additional_description: Option<String>,
|
||||||
|
/// Text visible on the PayU payment page (max. 80 chars).
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
visible_description: Option<String>,
|
||||||
|
/// Payment recipient name followed by payment description (order ID, ticket
|
||||||
|
/// number etc) visible on card statement (max. 22 chars). The name should
|
||||||
|
/// be easy to recognize by the cardholder (e.g "shop.com 124343"). If field
|
||||||
|
/// is not provided, static name configured by PayU will be used.
|
||||||
|
statement_description: Option<String>,
|
||||||
|
#[serde(flatten, skip_serializing_if = "Option::is_none")]
|
||||||
|
muct: Option<muct::MultiUseCartToken>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OrderCreateRequest {
|
impl OrderCreateRequest {
|
||||||
pub fn new<CustomerIp, Currency>(
|
pub fn build<CustomerIp, Currency, Description>(
|
||||||
buyer: Buyer,
|
buyer: Buyer,
|
||||||
customer_ip: CustomerIp,
|
customer_ip: CustomerIp,
|
||||||
currency: Currency,
|
currency: Currency,
|
||||||
) -> Self
|
description: Description,
|
||||||
|
) -> Result<Self>
|
||||||
where
|
where
|
||||||
CustomerIp: Into<String>,
|
CustomerIp: Into<String>,
|
||||||
Currency: Into<String>,
|
Currency: Into<String>,
|
||||||
|
Description: Into<String>,
|
||||||
{
|
{
|
||||||
Self {
|
let customer_ip = customer_ip.into();
|
||||||
|
if &customer_ip == "0.0.0.0" {
|
||||||
|
return Err(Error::CustomerIp);
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
ext_order_id: None,
|
||||||
notify_url: None,
|
notify_url: None,
|
||||||
customer_ip: customer_ip.into(),
|
continue_url: None,
|
||||||
|
customer_ip,
|
||||||
merchant_pos_id: 0.into(),
|
merchant_pos_id: 0.into(),
|
||||||
description: String::from(""),
|
description: description.into(),
|
||||||
currency_code: currency.into(),
|
currency_code: currency.into(),
|
||||||
total_amount: 0,
|
total_amount: 0,
|
||||||
buyer: Some(buyer),
|
buyer: Some(buyer),
|
||||||
products: Vec::new(),
|
products: Vec::new(),
|
||||||
order_create_date: None,
|
order_create_date: None,
|
||||||
}
|
validity_time: None,
|
||||||
|
additional_description: None,
|
||||||
|
visible_description: None,
|
||||||
|
statement_description: None,
|
||||||
|
muct: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ID of an order used in merchant system. Order identifier assigned by the
|
||||||
|
/// merchant. It enables merchants to find a specific order in their system.
|
||||||
|
/// This value must be unique within a single POS.
|
||||||
|
pub fn with_ext_order_id<S: Into<String>>(mut self, ext_order_id: S) -> Self {
|
||||||
|
self.ext_order_id = Some(ext_order_id.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Duration for the validity of an order (in seconds), during which time
|
||||||
|
/// payment must be made. Default value 86400.
|
||||||
|
pub fn with_validity_time(mut self, validity_time: u16) -> Self {
|
||||||
|
self.validity_time = Some(validity_time);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Additional description of the order.
|
||||||
|
pub fn with_additional_description<S: Into<String>>(
|
||||||
|
mut self,
|
||||||
|
additional_description: S,
|
||||||
|
) -> Self {
|
||||||
|
self.additional_description = Some(additional_description.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Text visible on the PayU payment page (max. 80 chars).
|
||||||
|
pub fn with_visible_description(mut self, visible_description: &str) -> Self {
|
||||||
|
let visible_description = if visible_description.len() > 60 {
|
||||||
|
&visible_description[..60]
|
||||||
|
} else {
|
||||||
|
visible_description
|
||||||
|
};
|
||||||
|
self.visible_description = Some(String::from(visible_description));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_multi_use_token(
|
||||||
|
mut self,
|
||||||
|
recurring: muct::Recurring,
|
||||||
|
card_on_file: muct::CardOnFile,
|
||||||
|
) -> Self {
|
||||||
|
self.muct = Some(muct::MultiUseCartToken {
|
||||||
|
recurring,
|
||||||
|
card_on_file,
|
||||||
|
});
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_products<Products>(mut self, products: Products) -> Self
|
pub fn with_products<Products>(mut self, products: Products) -> Self
|
||||||
|
Loading…
Reference in New Issue
Block a user