add cancel order flow - untested

This commit is contained in:
creating2morrow 2023-12-13 21:01:01 -05:00
parent 92ccd3836e
commit bb492fce8c
7 changed files with 208 additions and 27 deletions

View file

@ -1,3 +1,5 @@
#![allow(non_snake_case)]
use rocket::{ use rocket::{
get, get,
http::Status, http::Status,

View file

@ -1,3 +1,5 @@
#![allow(non_snake_case)]
use rocket::{ use rocket::{
get, get,
http::Status, http::Status,

View file

@ -5,15 +5,11 @@ use crate::{
db, db,
gpg, gpg,
i2p, i2p,
models::*, models::*,
monero, monero,
order, order,
product, product,
reqres::{ reqres,
self,
FinalizeOrderResponse,
},
utils, utils,
}; };
use log::{ use log::{
@ -24,7 +20,7 @@ use log::{
use rocket::serde::json::Json; use rocket::serde::json::Json;
pub enum StatusType { pub enum StatusType {
_Cancelled, Cancelled,
Delivered, Delivered,
MultisigMissing, MultisigMissing,
MulitsigComplete, MulitsigComplete,
@ -34,7 +30,7 @@ pub enum StatusType {
impl StatusType { impl StatusType {
pub fn value(&self) -> String { pub fn value(&self) -> String {
match *self { match *self {
StatusType::_Cancelled => String::from("Cancelled"), StatusType::Cancelled => String::from("Cancelled"),
StatusType::Delivered => String::from("Delivered"), StatusType::Delivered => String::from("Delivered"),
StatusType::MultisigMissing => String::from("MultisigMissing"), StatusType::MultisigMissing => String::from("MultisigMissing"),
StatusType::MulitsigComplete => String::from("MulitsigComplete"), StatusType::MulitsigComplete => String::from("MulitsigComplete"),
@ -163,7 +159,10 @@ pub fn find_all_backup() -> Vec<Order> {
let mut orders: Vec<Order> = Vec::new(); let mut orders: Vec<Order> = Vec::new();
for o in i_v { for o in i_v {
let order: Order = find(&o); 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); orders.push(order);
} }
} }
@ -256,6 +255,39 @@ pub async fn secure_retrieval(orid: &String, signature: &String) -> Order {
m_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 /// Check for import multisig info, validate block time and that the
/// ///
/// order wallet has been funded properly. Update the order to multisig complete /// 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() { if nasr_order.is_err() {
return Default::default(); return Default::default();
} }
FinalizeOrderResponse { reqres::FinalizeOrderResponse {
delivery_info: delivery_info.to_vec(), delivery_info: delivery_info.to_vec(),
orid: String::from(orid), orid: String::from(orid),
vendor_update_success: false, 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` /// Vendor will very txset submission and then update the order to `Delivered`
/// ///
/// status type. Then customer will update the status on the neveko instanced /// status type. Then customer will update the status on the neveko instanced
/// ///
/// upon a `vendor_update_success: true` response /// upon a `vendor_update_success: true` response
pub async fn finalize_order(orid: &String) -> reqres::FinalizeOrderResponse { pub async fn finalize_order(orid: &String) -> reqres::FinalizeOrderResponse {
info!("finalizing order: {}", orid); info!("finalizing order: {}", orid);
reqres::FinalizeOrderResponse { reqres::FinalizeOrderResponse {
..Default::default() ..Default::default()
} }
@ -433,7 +464,7 @@ pub async fn transmit_ship_request(
contact: &String, contact: &String,
jwp: &String, jwp: &String,
orid: &String, orid: &String,
) -> Result<FinalizeOrderResponse, Box<dyn Error>> { ) -> Result<reqres::FinalizeOrderResponse, Box<dyn Error>> {
info!("executing transmit_ship_request"); info!("executing transmit_ship_request");
let host = utils::get_i2p_http_proxy(); let host = utils::get_i2p_http_proxy();
let proxy = reqwest::Proxy::http(&host)?; let proxy = reqwest::Proxy::http(&host)?;
@ -445,7 +476,7 @@ pub async fn transmit_ship_request(
.await .await
{ {
Ok(response) => { Ok(response) => {
let res = response.json::<FinalizeOrderResponse>().await; let res = response.json::<reqres::FinalizeOrderResponse>().await;
debug!("ship request response: {:?}", res); debug!("ship request response: {:?}", res);
match res { match res {
Ok(r) => Ok(r), Ok(r) => Ok(r),
@ -520,6 +551,28 @@ pub async fn trigger_ship_request(contact: &String, jwp: &String, orid: &String)
unwrap_order 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 /// Decomposition trigger for the shipping request
pub async fn d_trigger_ship_request(contact: &String, orid: &String) -> Order { 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 // 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 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) { pub async fn init_mediator_wallet(orid: &String) {
let password = utils::empty_string(); let password = utils::empty_string();
let m_wallet = monero::create_wallet(orid, &password).await; let m_wallet = monero::create_wallet(orid, &password).await;

View file

@ -113,6 +113,8 @@ pub struct MarketApp {
submit_order_rx: Receiver<models::Order>, submit_order_rx: Receiver<models::Order>,
_submit_txset_tx: Sender<bool>, _submit_txset_tx: Sender<bool>,
_submit_txset_rx: Receiver<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_tx: Sender<models::Order>,
// ship_request_rx: Receiver<models::Order>, // ship_request_rx: Receiver<models::Order>,
s_contact: models::Contact, 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 (get_vendor_product_tx, get_vendor_product_rx) = std::sync::mpsc::channel();
let (submit_order_tx, submit_order_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 (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_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 (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(); 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, _refresh_on_delete_product_rx,
s_contact: Default::default(), s_contact: Default::default(),
s_order: Default::default(), s_order: Default::default(),
cancel_request_rx,
cancel_request_tx,
// ship_request_rx, // ship_request_rx,
// ship_request_tx, // ship_request_tx,
submit_order_rx, submit_order_rx,
@ -312,7 +317,7 @@ impl eframe::App for MarketApp {
// TODO(c2m): automated trigger for nasr worked successfully // TODO(c2m): automated trigger for nasr worked successfully
// doesn't seem like this is really needed anymore? // doesn't seem like this is really needed anymore?
// if let Ok(shipped) = self.ship_request_rx.try_recv() { // if let Ok(shipped) = self.ship_request_rx.try_recv() {
// if shipped.status != order::StatusType::Shipped.value() { // if shipped.status != order::StatusType::Shipped.value() {
// log::error!("failure to obtain shipment please contact vendor") // log::error!("failure to obtain shipment please contact vendor")
@ -320,6 +325,13 @@ impl eframe::App for MarketApp {
// self.is_loading = false; // 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 // Vendor status window
//----------------------------------------------------------------------------------- //-----------------------------------------------------------------------------------
let mut is_showing_vendor_status = self.is_showing_vendor_status; 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 // async trigger for signing and submitted the txset
// do something // do something
release_txset( release_txset(
utils::empty_string(), utils::empty_string(),
utils::empty_string(), utils::empty_string(),
ctx.clone(), ctx.clone(),
utils::empty_string(), utils::empty_string(),
self._submit_txset_tx.clone() self._submit_txset_tx.clone(),
); );
} }
if ui.button("Check").clicked() { if ui.button("Check").clicked() {
@ -833,12 +845,14 @@ impl eframe::App for MarketApp {
if ui.button("DINFO").clicked() { if ui.button("DINFO").clicked() {
let e_info: String = utils::search_gui_db( let e_info: String = utils::search_gui_db(
String::from(neveko_core::DELIVERY_INFO_DB_KEY), 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 d_bytes = gpg::decrypt(&String::from(&o.orid), &bytes);
let dinfo: String = String::from_utf8(d_bytes.unwrap_or(Vec::new())) let dinfo: String =
.unwrap_or(utils::empty_string()); String::from_utf8(d_bytes.unwrap_or(Vec::new()))
.unwrap_or(utils::empty_string());
self.decrypted_delivery_info = dinfo; self.decrypted_delivery_info = dinfo;
self.is_showing_decrypted_delivery_info = true; self.is_showing_decrypted_delivery_info = true;
} }
@ -847,7 +861,18 @@ impl eframe::App for MarketApp {
ui.style_mut().wrap = Some(false); ui.style_mut().wrap = Some(false);
ui.horizontal(|ui| { ui.horizontal(|ui| {
if ui.button("Cancel").clicked() { 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 // Request mediator and vendor while we're at it
// Will coordinating send this on make requests next // Will coordinating send this on make requests next
let s = db::Interface::async_open().await; let s = db::Interface::async_open().await;
let m_msig_key = format!( 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(); ctx.request_repaint();
} }
// TODO(c2m): do we need a manual trigger for the shipping request? // TODO(c2m): do we need a manual trigger for the shipping request?
// fn shipping_req( // 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 // 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( fn validate_msig_step(
mediator: &String, mediator: &String,
orid: &String, orid: &String,
@ -2263,7 +2303,7 @@ fn release_txset(
tx: Sender<bool>, tx: Sender<bool>,
) { ) {
tokio::spawn(async move { tokio::spawn(async move {
log::info!("async release txset"); log::info!("async release txset");
let lookup = order::find(&orid); let lookup = order::find(&orid);
let submit = order::sign_and_submit_multisig(&orid, &lookup.vend_msig_txset).await; let submit = order::sign_and_submit_multisig(&orid, &lookup.vend_msig_txset).await;
if submit.result.tx_hash_list.is_empty() { if submit.result.tx_hash_list.is_empty() {
@ -2271,7 +2311,7 @@ fn release_txset(
let _ = tx.send(false); let _ = tx.send(false);
return; 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 // to verify the txset was submitted successfully
// return boolean. // return boolean.
@ -2280,4 +2320,4 @@ fn release_txset(
ctx.request_repaint(); ctx.request_repaint();
todo!() todo!()
}); });
} }

View file

@ -1,3 +1,5 @@
#![allow(non_snake_case)]
use rocket::{ use rocket::{
get, get,
http::Status, http::Status,

View file

@ -1,3 +1,5 @@
#![allow(non_snake_case)]
use rocket::{ use rocket::{
get, get,
http::Status, http::Status,

View file

@ -1,3 +1,5 @@
#![allow(non_snake_case)]
use rocket::{ use rocket::{
catch, catch,
get, get,
@ -219,6 +221,24 @@ pub async fn trigger_nasr(
Custom(Status::Ok, Json(order)) 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) /// Create a dispute (customer)
#[post("/create", data = "<dispute>")] #[post("/create", data = "<dispute>")]
pub async fn create_dispute( pub async fn create_dispute(