diff --git a/neveko-auth/src/controller.rs b/neveko-auth/src/controller.rs index f52e013..350b4a3 100644 --- a/neveko-auth/src/controller.rs +++ b/neveko-auth/src/controller.rs @@ -1,3 +1,5 @@ +#![allow(non_snake_case)] + use rocket::{ get, http::Status, diff --git a/neveko-contact/src/controller.rs b/neveko-contact/src/controller.rs index 4323688..7f6eb13 100644 --- a/neveko-contact/src/controller.rs +++ b/neveko-contact/src/controller.rs @@ -1,3 +1,5 @@ +#![allow(non_snake_case)] + use rocket::{ get, http::Status, diff --git a/neveko-core/src/order.rs b/neveko-core/src/order.rs index cf5bf43..30bbc3e 100644 --- a/neveko-core/src/order.rs +++ b/neveko-core/src/order.rs @@ -5,15 +5,11 @@ use crate::{ db, gpg, i2p, - models::*, monero, order, product, - reqres::{ - self, - FinalizeOrderResponse, - }, + reqres, utils, }; use log::{ @@ -24,7 +20,7 @@ use log::{ use rocket::serde::json::Json; pub enum StatusType { - _Cancelled, + Cancelled, Delivered, MultisigMissing, MulitsigComplete, @@ -34,7 +30,7 @@ pub enum StatusType { impl StatusType { pub fn value(&self) -> String { match *self { - StatusType::_Cancelled => String::from("Cancelled"), + StatusType::Cancelled => String::from("Cancelled"), StatusType::Delivered => String::from("Delivered"), StatusType::MultisigMissing => String::from("MultisigMissing"), StatusType::MulitsigComplete => String::from("MulitsigComplete"), @@ -163,7 +159,10 @@ pub fn find_all_backup() -> Vec<Order> { let mut orders: Vec<Order> = Vec::new(); for o in i_v { let order: Order = find(&o); - if order.orid != utils::empty_string() { + let visible = order.orid != utils::empty_string() + && order.status != order::StatusType::Delivered.value() + && order.status != order::StatusType::Cancelled.value(); + if visible { orders.push(order); } } @@ -256,6 +255,39 @@ pub async fn secure_retrieval(orid: &String, signature: &String) -> Order { m_order } +/// In order for the order (...ha) to only be cancelled by the customer +/// +/// they must sign the order id with their NEVEKO wallet instance. +pub async fn cancel_order(orid: &String, signature: &String) -> Order { + info!("cancel order {}", orid); + // get customer address for NEVEKO NOT order wallet + let mut 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 wallet_password = + std::env::var(crate::MONERO_WALLET_PASSWORD).unwrap_or(String::from("password")); + let wallet_name = String::from(crate::APP_NAME); + monero::open_wallet(&wallet_name, &wallet_password).await; + let is_valid_signature = monero::verify(xmr_address, id, sig).await; + monero::close_wallet(&wallet_name, &wallet_password).await; + if !is_valid_signature { + return Default::default(); + } + // update the order status and send to customer + m_order.status = order::StatusType::Cancelled.value(); + order::modify(Json(m_order)); + order::find(&orid) +} + /// Check for import multisig info, validate block time and that the /// /// order wallet has been funded properly. Update the order to multisig complete @@ -375,7 +407,7 @@ pub async fn upload_delivery_info( if nasr_order.is_err() { return Default::default(); } - FinalizeOrderResponse { + reqres::FinalizeOrderResponse { delivery_info: delivery_info.to_vec(), orid: String::from(orid), vendor_update_success: false, @@ -383,14 +415,13 @@ pub async fn upload_delivery_info( } /// Vendor will very txset submission and then update the order to `Delivered` -/// +/// /// status type. Then customer will update the status on the neveko instanced -/// +/// /// upon a `vendor_update_success: true` response pub async fn finalize_order(orid: &String) -> reqres::FinalizeOrderResponse { info!("finalizing order: {}", orid); - reqres::FinalizeOrderResponse { ..Default::default() } @@ -433,7 +464,7 @@ pub async fn transmit_ship_request( contact: &String, jwp: &String, orid: &String, -) -> Result<FinalizeOrderResponse, Box<dyn Error>> { +) -> Result<reqres::FinalizeOrderResponse, Box<dyn Error>> { info!("executing transmit_ship_request"); let host = utils::get_i2p_http_proxy(); let proxy = reqwest::Proxy::http(&host)?; @@ -445,7 +476,7 @@ pub async fn transmit_ship_request( .await { Ok(response) => { - let res = response.json::<FinalizeOrderResponse>().await; + let res = response.json::<reqres::FinalizeOrderResponse>().await; debug!("ship request response: {:?}", res); match res { Ok(r) => Ok(r), @@ -520,6 +551,28 @@ pub async fn trigger_ship_request(contact: &String, jwp: &String, orid: &String) unwrap_order } +/// A post-decomposition trigger for the cancel request so that the logic +/// +/// can be executed from the gui. +pub async fn trigger_cancel_request(contact: &String, jwp: &String, orid: &String) -> Order { + info!("executing trigger_cancel_request"); + let data = String::from(orid); + let wallet_password = + std::env::var(crate::MONERO_WALLET_PASSWORD).unwrap_or(String::from("password")); + monero::open_wallet(&String::from(crate::APP_NAME), &wallet_password).await; + let pre_sign = monero::sign(data).await; + monero::close_wallet(&String::from(crate::APP_NAME), &wallet_password).await; + let order = transmit_cancel_request(contact, jwp, orid, &pre_sign.result.signature).await; + // cache order request to db + if order.is_err() { + log::error!("failed to trigger cancel request"); + return Default::default(); + } + let unwrap_order: Order = order.unwrap(); + backup(&unwrap_order); + unwrap_order +} + /// Decomposition trigger for the shipping request pub async fn d_trigger_ship_request(contact: &String, orid: &String) -> Order { // ugh, sorry seems we need to get jwp for vendor from fts cache @@ -544,6 +597,66 @@ pub async fn d_trigger_ship_request(contact: &String, orid: &String) -> Order { trigger } +/// Executes POST /order/cancel/orid/signature +/// +/// cancelling the order on the vendor side. +/// +/// Customer needs to verify the response and update their lmdb. +/// +/// see `cancel_order` +pub async fn transmit_cancel_request( + contact: &String, + jwp: &String, + orid: &String, + signature: &String, +) -> Result<Order, Box<dyn Error>> { + info!("executing transmit_cancel_request"); + let host = utils::get_i2p_http_proxy(); + let proxy = reqwest::Proxy::http(&host)?; + let client = reqwest::Client::builder().proxy(proxy).build(); + match client? + .post(format!( + "http://{}/market/order/cancel/{}/{}", + contact, orid, signature + )) + .header("proof", jwp) + .send() + .await + { + Ok(response) => { + let res = response.json::<Order>().await; + debug!("cancel order response: {:?}", res); + match res { + Ok(r) => Ok(r), + _ => Ok(Default::default()), + } + } + Err(e) => { + error!("failed to cancel order due to: {:?}", e); + Ok(Default::default()) + } + } +} + +/// Decomposition trigger for the shipping request +pub async fn d_trigger_cancel_request(contact: &String, orid: &String) -> Order { + // ugh, sorry seems we need to get jwp for vendor from fts cache + // get jwp from db + let s = db::Interface::async_open().await; + let k = format!("{}-{}", crate::FTS_JWP_DB_KEY, &contact); + let jwp = db::Interface::async_read(&s.env, &s.handle, &k).await; + info!("executing d_trigger_cancel_request"); + // request cancel if the order status is not MultisigComplete + let order: Order = order::find(&orid); + if order.status != order::StatusType::MulitsigComplete.value() { + let trigger = trigger_cancel_request(contact, &jwp, orid).await; + if trigger.status == order::StatusType::Cancelled.value() { + return trigger; + } + } + Default::default() +} + pub async fn init_mediator_wallet(orid: &String) { let password = utils::empty_string(); let m_wallet = monero::create_wallet(orid, &password).await; diff --git a/neveko-gui/src/apps/market.rs b/neveko-gui/src/apps/market.rs index d3377df..6536135 100644 --- a/neveko-gui/src/apps/market.rs +++ b/neveko-gui/src/apps/market.rs @@ -113,6 +113,8 @@ pub struct MarketApp { submit_order_rx: Receiver<models::Order>, _submit_txset_tx: Sender<bool>, _submit_txset_rx: Receiver<bool>, + cancel_request_tx: Sender<models::Order>, + cancel_request_rx: Receiver<models::Order>, // ship_request_tx: Sender<models::Order>, // ship_request_rx: Receiver<models::Order>, s_contact: models::Contact, @@ -137,6 +139,7 @@ impl Default for MarketApp { let (get_vendor_product_tx, get_vendor_product_rx) = std::sync::mpsc::channel(); let (submit_order_tx, submit_order_rx) = std::sync::mpsc::channel(); // let (ship_request_tx, ship_request_rx) = std::sync::mpsc::channel(); + let (cancel_request_tx, cancel_request_rx) = std::sync::mpsc::channel(); let (our_prepare_info_tx, our_prepare_info_rx) = std::sync::mpsc::channel(); let (our_make_info_tx, our_make_info_rx) = std::sync::mpsc::channel(); let (order_xmr_address_tx, order_xmr_address_rx) = std::sync::mpsc::channel(); @@ -208,6 +211,8 @@ impl Default for MarketApp { _refresh_on_delete_product_rx, s_contact: Default::default(), s_order: Default::default(), + cancel_request_rx, + cancel_request_tx, // ship_request_rx, // ship_request_tx, submit_order_rx, @@ -312,7 +317,7 @@ impl eframe::App for MarketApp { // TODO(c2m): automated trigger for nasr worked successfully // doesn't seem like this is really needed anymore? - + // if let Ok(shipped) = self.ship_request_rx.try_recv() { // if shipped.status != order::StatusType::Shipped.value() { // log::error!("failure to obtain shipment please contact vendor") @@ -320,6 +325,13 @@ impl eframe::App for MarketApp { // self.is_loading = false; // } + if let Ok(cancelled) = self.cancel_request_rx.try_recv() { + if cancelled.status != order::StatusType::Cancelled.value() { + log::error!("failure to cancel shipment please contact vendor") + } + self.is_loading = false; + } + // Vendor status window //----------------------------------------------------------------------------------- let mut is_showing_vendor_status = self.is_showing_vendor_status; @@ -689,11 +701,11 @@ impl eframe::App for MarketApp { // async trigger for signing and submitted the txset // do something release_txset( - utils::empty_string(), + utils::empty_string(), utils::empty_string(), ctx.clone(), utils::empty_string(), - self._submit_txset_tx.clone() + self._submit_txset_tx.clone(), ); } if ui.button("Check").clicked() { @@ -833,12 +845,14 @@ impl eframe::App for MarketApp { if ui.button("DINFO").clicked() { let e_info: String = utils::search_gui_db( String::from(neveko_core::DELIVERY_INFO_DB_KEY), - String::from(&o.orid) + String::from(&o.orid), ); - let bytes = hex::decode(e_info.into_bytes()).unwrap_or(Vec::new()); + let bytes = + hex::decode(e_info.into_bytes()).unwrap_or(Vec::new()); let d_bytes = gpg::decrypt(&String::from(&o.orid), &bytes); - let dinfo: String = String::from_utf8(d_bytes.unwrap_or(Vec::new())) - .unwrap_or(utils::empty_string()); + let dinfo: String = + String::from_utf8(d_bytes.unwrap_or(Vec::new())) + .unwrap_or(utils::empty_string()); self.decrypted_delivery_info = dinfo; self.is_showing_decrypted_delivery_info = true; } @@ -847,7 +861,18 @@ impl eframe::App for MarketApp { ui.style_mut().wrap = Some(false); ui.horizontal(|ui| { if ui.button("Cancel").clicked() { - // TODO(c2m): Cancel order logic + let vendor_prefix = String::from(crate::GUI_OVL_DB_KEY); + let vendor = utils::search_gui_db( + vendor_prefix, + self.m_order.orid.clone(), + ); + self.is_loading = true; + cancel_order_req( + self.cancel_request_tx.clone(), + ctx.clone(), + &self.m_order.orid, + &vendor, + ) } }); }); @@ -1704,7 +1729,6 @@ fn send_prepare_info_req( ); // Request mediator and vendor while we're at it // Will coordinating send this on make requests next - let s = db::Interface::async_open().await; let m_msig_key = format!( "{}-{}-{}", @@ -2217,7 +2241,6 @@ fn send_import_info_req(tx: Sender<String>, ctx: egui::Context, orid: &String, v ctx.request_repaint(); } - // TODO(c2m): do we need a manual trigger for the shipping request? // fn shipping_req( @@ -2239,6 +2262,23 @@ fn send_import_info_req(tx: Sender<String>, ctx: egui::Context, orid: &String, v // } // End Async fn requests +fn cancel_order_req( + tx: Sender<models::Order>, + ctx: egui::Context, + orid: &String, + contact: &String, +) { + let ship_orid = String::from(orid); + let vendor_i2p = String::from(contact); + tokio::spawn(async move { + log::info!("cancel order req: {}", ship_orid); + let order = order::d_trigger_cancel_request(&vendor_i2p, &ship_orid).await; + let _ = tx.send(order); + ctx.request_repaint(); + }); +} +// End Async fn requests + fn validate_msig_step( mediator: &String, orid: &String, @@ -2263,7 +2303,7 @@ fn release_txset( tx: Sender<bool>, ) { tokio::spawn(async move { - log::info!("async release txset"); + log::info!("async release txset"); let lookup = order::find(&orid); let submit = order::sign_and_submit_multisig(&orid, &lookup.vend_msig_txset).await; if submit.result.tx_hash_list.is_empty() { @@ -2271,7 +2311,7 @@ fn release_txset( let _ = tx.send(false); return; } - // TODO(next commit): we need to build an API that tells the vendor + // TODO(c2m): we need to build an API that tells the vendor // to verify the txset was submitted successfully // return boolean. @@ -2280,4 +2320,4 @@ fn release_txset( ctx.request_repaint(); todo!() }); -} \ No newline at end of file +} diff --git a/neveko-market/src/controller.rs b/neveko-market/src/controller.rs index 5b6584f..566cd3a 100644 --- a/neveko-market/src/controller.rs +++ b/neveko-market/src/controller.rs @@ -1,3 +1,5 @@ +#![allow(non_snake_case)] + use rocket::{ get, http::Status, diff --git a/neveko-message/src/controller.rs b/neveko-message/src/controller.rs index 9c82e6c..355bb4b 100644 --- a/neveko-message/src/controller.rs +++ b/neveko-message/src/controller.rs @@ -1,3 +1,5 @@ +#![allow(non_snake_case)] + use rocket::{ get, http::Status, diff --git a/src/controller.rs b/src/controller.rs index 8d4deae..79fa4c4 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -1,3 +1,5 @@ +#![allow(non_snake_case)] + use rocket::{ catch, get, @@ -219,6 +221,24 @@ pub async fn trigger_nasr( Custom(Status::Ok, Json(order)) } +/// Customer cancel order logic. Must send `signature` +/// +/// which is the order id signed by the NEVEKO wallet. +/// +/// Protected: true +#[post("/order/cancel/<orid>/<signature>")] +pub async fn cancel_order( + orid: String, + signature: String, + _jwp: proof::PaymentProof, +) -> Custom<Json<models::Order>> { + let m_order = order::cancel_order(&orid, &signature).await; + if m_order.cid == utils::empty_string() { + return Custom(Status::BadRequest, Json(Default::default())); + } + Custom(Status::Created, Json(m_order)) +} + /// Create a dispute (customer) #[post("/create", data = "<dispute>")] pub async fn create_dispute(