use crate::app::context::AppContext;
use crate::db::find_dispute_by_order_id;
use crate::nip33::{create_platform_tag_values, new_dispute_event};
use crate::util::{enqueue_order_msg, get_order};
use mostro_core::prelude::*;
use nostr_sdk::prelude::*;
use sqlx_crud::traits::Crud;
use std::borrow::Cow;
use uuid::Uuid;
async fn publish_dispute_event(
ctx: &AppContext,
dispute: &Dispute,
my_keys: &Keys,
is_buyer_dispute: bool,
) -> Result<(), MostroError> {
let initiator = match is_buyer_dispute {
true => "buyer",
false => "seller",
};
let tags = Tags::from_list(vec![
Tag::custom(
TagKind::Custom(Cow::Borrowed("s")),
vec![dispute.status.to_string()],
),
Tag::custom(
TagKind::Custom(Cow::Borrowed("initiator")),
vec![initiator.to_string()],
),
Tag::custom(
TagKind::Custom(Cow::Borrowed("y")),
create_platform_tag_values(ctx.settings().mostro.name.as_deref()),
),
Tag::custom(
TagKind::Custom(Cow::Borrowed("z")),
vec!["dispute".to_string()],
),
]);
let event = new_dispute_event(my_keys, "", dispute.id.to_string(), tags)
.map_err(|_| MostroInternalErr(ServiceError::DisputeEventError))?;
tracing::info!("Publishing dispute event: {:#?}", event);
let client = ctx.nostr_client();
match client.send_event(&event).await {
Ok(_) => {
tracing::info!(
"Successfully published dispute event for dispute ID: {}",
dispute.id
);
Ok(())
}
Err(e) => {
tracing::error!("Failed to send dispute event: {}", e);
Err(MostroInternalErr(ServiceError::NostrError(e.to_string())))
}
}
}
fn get_counterpart_info(sender: &str, buyer: &str, seller: &str) -> Result<bool, CantDoReason> {
match sender {
s if s == buyer => Ok(true), s if s == seller => Ok(false), _ => Err(CantDoReason::InvalidPubkey),
}
}
async fn get_valid_order(ctx: &AppContext, msg: &Message) -> Result<Order, MostroError> {
let order = get_order(msg, ctx.pool()).await?;
if order.check_status(Status::Active).is_err() && order.check_status(Status::FiatSent).is_err()
{
return Err(MostroCantDo(CantDoReason::NotAllowedByStatus));
}
Ok(order)
}
async fn notify_dispute_to_users(
dispute: &Dispute,
msg: &Message,
order_id: Uuid,
counterpart_pubkey: PublicKey,
initiator_pubkey: PublicKey,
) -> Result<(), MostroError> {
enqueue_order_msg(
msg.get_inner_message_kind().request_id,
Some(order_id),
Action::DisputeInitiatedByPeer,
Some(Payload::Dispute(dispute.clone().id, None)),
counterpart_pubkey,
None,
)
.await;
enqueue_order_msg(
msg.get_inner_message_kind().request_id,
Some(order_id),
Action::DisputeInitiatedByYou,
Some(Payload::Dispute(dispute.clone().id, None)),
initiator_pubkey,
None,
)
.await;
Ok(())
}
pub async fn dispute_action(
ctx: &AppContext,
msg: Message,
event: &UnwrappedMessage,
my_keys: &Keys,
) -> Result<(), MostroError> {
let pool = ctx.pool();
let order_id = if let Some(order_id) = msg.get_inner_message_kind().id {
order_id
} else {
return Err(MostroInternalErr(ServiceError::InvalidOrderId));
};
if find_dispute_by_order_id(pool, order_id).await.is_ok() {
return Err(MostroInternalErr(ServiceError::DisputeAlreadyExists));
}
let mut order = get_valid_order(ctx, &msg).await?;
let (seller, buyer) = match (&order.seller_pubkey, &order.buyer_pubkey) {
(Some(seller), Some(buyer)) => (seller.to_owned(), buyer.to_owned()),
(None, _) => return Err(MostroInternalErr(ServiceError::InvalidPubkey)),
(_, None) => return Err(MostroInternalErr(ServiceError::InvalidPubkey)),
};
let message_sender = event.sender.to_string();
let is_buyer_dispute = match get_counterpart_info(&message_sender, &buyer, &seller) {
Ok(is_buyer_dispute) => is_buyer_dispute,
Err(cause) => return Err(MostroCantDo(cause)),
};
let dispute = Dispute::new(order_id, order.status.clone());
if order.setup_dispute(is_buyer_dispute).is_ok() {
order
.clone()
.update(pool)
.await
.map_err(|cause| MostroInternalErr(ServiceError::DbAccessError(cause.to_string())))?;
}
let dispute = dispute
.create(pool)
.await
.map_err(|cause| MostroInternalErr(ServiceError::DbAccessError(cause.to_string())))?;
let (initiator_pubkey, counterpart_pubkey) = if is_buyer_dispute {
(
&order
.get_buyer_pubkey()
.map_err(|_| MostroInternalErr(ServiceError::InvalidPubkey))?,
&order
.get_seller_pubkey()
.map_err(|_| MostroInternalErr(ServiceError::InvalidPubkey))?,
)
} else {
(
&order
.get_seller_pubkey()
.map_err(|_| MostroInternalErr(ServiceError::InvalidPubkey))?,
&order
.get_buyer_pubkey()
.map_err(|_| MostroInternalErr(ServiceError::InvalidPubkey))?,
)
};
notify_dispute_to_users(
&dispute,
&msg,
order_id,
*counterpart_pubkey,
*initiator_pubkey,
)
.await?;
publish_dispute_event(ctx, &dispute, my_keys, is_buyer_dispute)
.await
.map_err(|_| MostroInternalErr(ServiceError::DisputeEventError))?;
Ok(())
}
pub async fn close_dispute_after_user_resolution(
ctx: &AppContext,
order: &Order,
new_status: DisputeStatus,
my_keys: &Keys,
context: &str,
) {
let pool = ctx.pool();
if let Ok(mut dispute) = find_dispute_by_order_id(pool, order.id).await {
let dispute_id = dispute.id;
dispute.status = new_status.to_string();
if let Err(e) = dispute.update(pool).await {
tracing::error!(
"Failed to update dispute {} status after {}: {}",
dispute_id,
context,
e
);
} else {
tracing::info!(
"Dispute {} closed automatically after {} of order {}",
dispute_id,
context,
order.id
);
let dispute_initiator = match (order.seller_dispute, order.buyer_dispute) {
(true, false) => "seller",
(false, true) => "buyer",
_ => {
tracing::warn!(
"Dispute {} for order {} has inconsistent dispute flags (seller={}, buyer={}); \
publishing initiator as 'unknown'",
dispute_id,
order.id,
order.seller_dispute,
order.buyer_dispute,
);
"unknown"
}
};
let tags = Tags::from_list(vec![
Tag::custom(
TagKind::Custom(Cow::Borrowed("s")),
vec![new_status.to_string()],
),
Tag::custom(
TagKind::Custom(Cow::Borrowed("initiator")),
vec![dispute_initiator.to_string()],
),
Tag::custom(
TagKind::Custom(Cow::Borrowed("y")),
create_platform_tag_values(ctx.settings().mostro.name.as_deref()),
),
Tag::custom(
TagKind::Custom(Cow::Borrowed("z")),
vec!["dispute".to_string()],
),
]);
match new_dispute_event(my_keys, "", dispute_id.to_string(), tags) {
Ok(event) => {
let client = ctx.nostr_client();
tracing::info!("Publishing dispute close event: {:#?}", event);
if let Err(e) = client.send_event(&event).await {
tracing::error!("Failed to publish dispute close event: {}", e);
}
}
Err(e) => {
tracing::error!(
"Failed to create dispute close event for dispute {}: {}",
dispute_id,
e
);
}
}
}
}
}