mirror of
https://github.com/creating2morrow/neveko.git
synced 2025-01-21 18:24:33 +00:00
289 lines
11 KiB
Rust
289 lines
11 KiB
Rust
use crate::{
|
|
contact,
|
|
db,
|
|
gpg,
|
|
i2p,
|
|
message,
|
|
models::*,
|
|
monero,
|
|
product,
|
|
reqres,
|
|
utils,
|
|
};
|
|
use log::{
|
|
debug,
|
|
error,
|
|
info,
|
|
};
|
|
use rocket::serde::json::Json;
|
|
|
|
enum StatusType {
|
|
Delivered,
|
|
MultisigMissing,
|
|
MulitsigComplete,
|
|
Shipped,
|
|
}
|
|
|
|
impl StatusType {
|
|
pub fn value(&self) -> String {
|
|
match *self {
|
|
StatusType::Delivered => String::from("Delivered"),
|
|
StatusType::MultisigMissing => String::from("MultisigMissing"),
|
|
StatusType::MulitsigComplete => String::from("MulitsigComplete"),
|
|
StatusType::Shipped => String::from("Shipped"),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Create a intial order
|
|
pub async fn create(j_order: Json<reqres::OrderRequest>) -> Order {
|
|
info!("creating order");
|
|
let wallet_name = String::from(crate::APP_NAME);
|
|
let wallet_password =
|
|
std::env::var(crate::MONERO_WALLET_PASSWORD).unwrap_or(String::from("password"));
|
|
monero::close_wallet(&wallet_name, &wallet_password).await;
|
|
let ts = chrono::offset::Utc::now().timestamp();
|
|
let orid: String = format!("O{}", utils::generate_rnd());
|
|
let r_subaddress = monero::create_address().await;
|
|
let subaddress = r_subaddress.result.address;
|
|
let new_order = Order {
|
|
orid: String::from(&orid),
|
|
cid: String::from(&j_order.cid),
|
|
pid: String::from(&j_order.pid),
|
|
date: ts,
|
|
ship_address: j_order.ship_address.iter().cloned().collect(),
|
|
subaddress,
|
|
status: StatusType::MultisigMissing.value(),
|
|
quantity: j_order.quantity,
|
|
..Default::default()
|
|
};
|
|
debug!("insert order: {:?}", new_order);
|
|
let m_wallet = monero::create_wallet(&orid, &utils::empty_string()).await;
|
|
if !m_wallet {
|
|
error!("error creating msig wallet for order {}", &orid);
|
|
monero::close_wallet(&orid, &wallet_password).await;
|
|
return Default::default();
|
|
}
|
|
debug!("insert order: {:?}", &new_order);
|
|
let s = db::Interface::open();
|
|
let k = &new_order.orid;
|
|
db::Interface::write(&s.env, &s.handle, k, &Order::to_db(&new_order));
|
|
// in order to retrieve all orders, write keys to with ol
|
|
let list_key = format!("ol");
|
|
let r = db::Interface::read(&s.env, &s.handle, &String::from(&list_key));
|
|
if r == utils::empty_string() {
|
|
debug!("creating order index");
|
|
}
|
|
let order_list = [r, String::from(&orid)].join(",");
|
|
debug!("writing order index {} for id: {}", order_list, list_key);
|
|
db::Interface::write(&s.env, &s.handle, &String::from(list_key), &order_list);
|
|
monero::close_wallet(&orid, &wallet_password).await;
|
|
new_order
|
|
}
|
|
|
|
/// Lookup order
|
|
pub fn find(oid: &String) -> Order {
|
|
info!("find order: {}", &oid);
|
|
let s = db::Interface::open();
|
|
let r = db::Interface::read(&s.env, &s.handle, &String::from(oid));
|
|
if r == utils::empty_string() {
|
|
error!("order not found");
|
|
return Default::default();
|
|
}
|
|
Order::from_db(String::from(oid), r)
|
|
}
|
|
|
|
/// Lookup all orders from admin server
|
|
pub fn find_all() -> Vec<Order> {
|
|
let i_s = db::Interface::open();
|
|
let i_list_key = format!("ol");
|
|
let i_r = db::Interface::read(&i_s.env, &i_s.handle, &String::from(i_list_key));
|
|
if i_r == utils::empty_string() {
|
|
error!("order index not found");
|
|
}
|
|
let i_v_oid = i_r.split(",");
|
|
let i_v: Vec<String> = i_v_oid.map(|s| String::from(s)).collect();
|
|
let mut orders: Vec<Order> = Vec::new();
|
|
for o in i_v {
|
|
let order: Order = find(&o);
|
|
if order.orid != utils::empty_string() {
|
|
orders.push(order);
|
|
}
|
|
}
|
|
orders
|
|
}
|
|
|
|
/// Lookup all orders for customer
|
|
pub async fn find_all_customer_orders(cid: String) -> Vec<Order> {
|
|
info!("lookup orders for customer: {}", &cid);
|
|
let i_s = db::Interface::open();
|
|
let i_list_key = format!("ol");
|
|
let i_r = db::Interface::read(&i_s.env, &i_s.handle, &String::from(i_list_key));
|
|
if i_r == utils::empty_string() {
|
|
error!("order index not found");
|
|
}
|
|
let i_v_oid = i_r.split(",");
|
|
let i_v: Vec<String> = i_v_oid.map(|s| String::from(s)).collect();
|
|
let mut orders: Vec<Order> = Vec::new();
|
|
for o in i_v {
|
|
let order: Order = find(&o);
|
|
if order.orid != utils::empty_string() && order.cid == cid {
|
|
orders.push(order);
|
|
}
|
|
}
|
|
orders
|
|
}
|
|
|
|
/// Modify order from admin server
|
|
pub fn modify(o: Json<Order>) -> Order {
|
|
info!("modify order: {}", &o.orid);
|
|
let f_order: Order = find(&o.orid);
|
|
if f_order.orid == utils::empty_string() {
|
|
error!("order not found");
|
|
return Default::default();
|
|
}
|
|
let u_order = Order::update(String::from(&f_order.orid), &o);
|
|
let s = db::Interface::open();
|
|
db::Interface::delete(&s.env, &s.handle, &u_order.pid);
|
|
db::Interface::write(&s.env, &s.handle, &u_order.pid, &Order::to_db(&u_order));
|
|
return u_order;
|
|
}
|
|
|
|
/// Sign and submit multisig
|
|
pub async fn sign_and_submit_multisig(
|
|
orid: &String,
|
|
tx_data_hex: &String,
|
|
) -> reqres::XmrRpcSubmitMultisigResponse {
|
|
info!("signing and submitting multisig");
|
|
let r_sign: reqres::XmrRpcSignMultisigResponse =
|
|
monero::sign_multisig(String::from(tx_data_hex)).await;
|
|
let r_submit: reqres::XmrRpcSubmitMultisigResponse =
|
|
monero::submit_multisig(r_sign.result.tx_data_hex).await;
|
|
if r_submit.result.tx_hash_list.is_empty() {
|
|
error!("unable to submit payment for order: {}", orid);
|
|
}
|
|
r_submit
|
|
}
|
|
|
|
/// In order for the order (...ha) to only be accessed by the customer
|
|
///
|
|
/// they must sign the order id with their NEVEKO wallet instance. This means
|
|
///
|
|
/// that the mediator can see order id for disputes without being able to access
|
|
///
|
|
/// the details of said order.
|
|
pub async fn secure_retrieval(orid: &String, signature: &String) -> Order {
|
|
// get customer address for NEVEKO NOT order wallet
|
|
let m_order: Order = find(&orid);
|
|
let mut xmr_address: String = String::new();
|
|
let a_customers: Vec<Contact> = contact::find_all();
|
|
for customer in a_customers {
|
|
if customer.i2p_address == m_order.cid {
|
|
xmr_address = customer.xmr_address;
|
|
break;
|
|
}
|
|
}
|
|
// send address, orid and signature to verify()
|
|
let id: String = String::from(&m_order.orid);
|
|
let sig: String = String::from(signature);
|
|
let is_valid_signature = monero::verify(xmr_address, id, sig).await;
|
|
if !is_valid_signature {
|
|
return Default::default();
|
|
}
|
|
m_order
|
|
}
|
|
|
|
/// Check for import multisig info, validate block time and that the
|
|
///
|
|
/// order wallet has been funded properly. Update the order to multisig complete
|
|
pub async fn validate_order_for_ship(orid: &String) -> bool {
|
|
info!("validating order for shipment");
|
|
let mut m_order: Order = find(orid);
|
|
let m_product: Product = product::find(&m_order.pid);
|
|
let price = m_product.price;
|
|
let total = price * m_order.quantity;
|
|
// import multisig info
|
|
let s = db::Interface::open();
|
|
let key = format!("export-{}-{}", orid, &m_order.cid);
|
|
let info_str = db::Interface::async_read(&s.env, &s.handle, &key).await;
|
|
let info_split = info_str.split(":");
|
|
let v_info: Vec<String> = info_split.map(|s| String::from(s)).collect();
|
|
let wallet_password = utils::empty_string();
|
|
monero::open_wallet(&orid, &wallet_password).await;
|
|
let r_import = monero::import_multisig_info(v_info).await;
|
|
// check balance and unlock_time
|
|
let r_balance = monero::get_balance().await;
|
|
// update the order status to multisig complete
|
|
let ready_to_ship: bool = r_import.result.n_outputs > 0
|
|
&& r_balance.result.balance >= total as u128
|
|
&& r_balance.result.blocks_to_unlock < monero::LockTimeLimit::Blocks.value();
|
|
if ready_to_ship {
|
|
m_order.status = StatusType::MulitsigComplete.value();
|
|
db::Interface::async_delete(&s.env, &s.handle, &m_order.orid).await;
|
|
db::Interface::async_write(&s.env, &s.handle, &m_order.orid, &Order::to_db(&m_order)).await;
|
|
}
|
|
ready_to_ship
|
|
}
|
|
|
|
/// Write encrypted delivery info to lmdb. Once the customer releases the signed
|
|
/// txset
|
|
///
|
|
/// they will have access to this information (tracking number, locker code,
|
|
/// etc.)
|
|
pub async fn upload_delivery_info(orid: &String, delivery_info: &Vec<u8>) {
|
|
info!("uploading delivery info");
|
|
let name = i2p::get_destination(None);
|
|
let e_delivery_info: Vec<u8> = gpg::encrypt(name, &delivery_info).unwrap_or(Vec::new());
|
|
if e_delivery_info.is_empty() {
|
|
error!("unable to encrypt delivery info");
|
|
}
|
|
// write delivery info {delivery}-{order id}
|
|
let s = db::Interface::async_open().await;
|
|
let k = format!("delivery-{}", orid);
|
|
let data = hex::encode(e_delivery_info);
|
|
db::Interface::async_write(&s.env, &s.handle, &k, &data).await;
|
|
// update the order
|
|
let mut m_order: Order = find(orid);
|
|
m_order.status = StatusType::Shipped.value();
|
|
db::Interface::async_delete(&s.env, &s.handle, &m_order.orid).await;
|
|
db::Interface::async_write(&s.env, &s.handle, &m_order.orid, &Order::to_db(&m_order)).await;
|
|
}
|
|
|
|
/// The vendor will first search for a encrypted multisig message in the form
|
|
///
|
|
/// txset-{order id}-{.b32.i2p}
|
|
pub async fn finalize_order(orid: &String) -> reqres::FinalizeOrderResponse {
|
|
info!("finalizing order");
|
|
let mut m_order: Order = find(orid);
|
|
let s = db::Interface::async_open().await;
|
|
let key = format!("{}-{}-{}", message::TXSET_MSIG, orid, &m_order.cid);
|
|
let txset = db::Interface::async_read(&s.env, &s.handle, &key).await;
|
|
// describe transer to check amount, address and unlock_time
|
|
let r_describe: reqres::XmrRpcDescribeTransferResponse =
|
|
monero::describe_transfer(&txset).await;
|
|
let m_product: Product = product::find(&m_order.pid);
|
|
let total: u128 = m_product.price * m_order.quantity;
|
|
let description: &reqres::TransferDescription = &r_describe.result.desc[0];
|
|
let is_valid_payment: bool = description.amount_out + description.fee >= total
|
|
&& description.unlock_time < monero::LockTimeLimit::Blocks.value();
|
|
if !is_valid_payment {
|
|
return Default::default();
|
|
}
|
|
let r_submit: reqres::XmrRpcSubmitMultisigResponse =
|
|
sign_and_submit_multisig(orid, &txset).await;
|
|
if r_submit.result.tx_hash_list.is_empty() {
|
|
return Default::default();
|
|
}
|
|
// lookup delivery info
|
|
let delivery_key = format!("delivery-{}", orid);
|
|
let r_delivery_info: String = db::Interface::async_read(&s.env, &s.handle, &delivery_key).await;
|
|
let delivery_info: Vec<u8> = hex::decode(r_delivery_info).unwrap_or(Vec::new());
|
|
// update the order
|
|
m_order.status = StatusType::Delivered.value();
|
|
db::Interface::async_delete(&s.env, &s.handle, &m_order.orid).await;
|
|
db::Interface::async_write(&s.env, &s.handle, &m_order.orid, &Order::to_db(&m_order)).await;
|
|
reqres::FinalizeOrderResponse {
|
|
orid: String::from(orid),
|
|
delivery_info,
|
|
}
|
|
}
|