Add check-expired-invoice command

This commit is contained in:
silverpill 2022-11-25 19:11:50 +00:00
parent 109168b5eb
commit fde8309bb9
9 changed files with 115 additions and 13 deletions

View file

@ -85,3 +85,9 @@ Create Monero wallet:
```shell ```shell
mitractl create-monero-wallet "mitra-wallet" "passw0rd" mitractl create-monero-wallet "mitra-wallet" "passw0rd"
``` ```
Check expired invoice:
```shell
mitractl check-expired-invoice 0184b062-d8d5-cbf1-a71b-6d1aafbae2ab
```

View file

@ -36,6 +36,7 @@ async fn main() {
SubCommand::UpdateCurrentBlock(cmd) => cmd.execute(&config, db_client).await.unwrap(), SubCommand::UpdateCurrentBlock(cmd) => cmd.execute(&config, db_client).await.unwrap(),
SubCommand::ResetSubscriptions(cmd) => cmd.execute(&config, db_client).await.unwrap(), SubCommand::ResetSubscriptions(cmd) => cmd.execute(&config, db_client).await.unwrap(),
SubCommand::CreateMoneroWallet(cmd) => cmd.execute(&config).await.unwrap(), SubCommand::CreateMoneroWallet(cmd) => cmd.execute(&config).await.unwrap(),
SubCommand::CheckExpiredInvoice(cmd) => cmd.execute(&config, db_client).await.unwrap(),
_ => panic!(), _ => panic!(),
}; };
}, },

View file

@ -29,7 +29,10 @@ use crate::models::users::queries::{
get_user_by_id, get_user_by_id,
set_user_password, set_user_password,
}; };
use crate::monero::wallet::create_monero_wallet; use crate::monero::{
helpers::check_expired_invoice,
wallet::create_monero_wallet,
};
use crate::utils::{ use crate::utils::{
crypto_rsa::{ crypto_rsa::{
generate_rsa_key, generate_rsa_key,
@ -64,6 +67,7 @@ pub enum SubCommand {
UpdateCurrentBlock(UpdateCurrentBlock), UpdateCurrentBlock(UpdateCurrentBlock),
ResetSubscriptions(ResetSubscriptions), ResetSubscriptions(ResetSubscriptions),
CreateMoneroWallet(CreateMoneroWallet), CreateMoneroWallet(CreateMoneroWallet),
CheckExpiredInvoice(CheckExpiredInvoice),
} }
/// Generate RSA private key /// Generate RSA private key
@ -404,3 +408,27 @@ impl CreateMoneroWallet {
Ok(()) Ok(())
} }
} }
/// Check expired invoice
#[derive(Parser)]
pub struct CheckExpiredInvoice {
id: Uuid,
}
impl CheckExpiredInvoice {
pub async fn execute(
&self,
config: &Config,
db_client: &impl GenericClient,
) -> Result<(), Error> {
let monero_config = config.blockchain()
.and_then(|conf| conf.monero_config())
.ok_or(anyhow!("monero configuration not found"))?;
check_expired_invoice(
monero_config,
db_client,
&self.id,
).await?;
Ok(())
}
}

View file

@ -156,6 +156,6 @@ mod tests {
assert_eq!(invoice.chain_id, chain_id); assert_eq!(invoice.chain_id, chain_id);
assert_eq!(invoice.payment_address, payment_address); assert_eq!(invoice.payment_address, payment_address);
assert_eq!(invoice.amount, amount); assert_eq!(invoice.amount, amount);
assert!(matches!(invoice.invoice_status, InvoiceStatus::Open)); assert_eq!(invoice.invoice_status, InvoiceStatus::Open);
} }
} }

View file

@ -8,7 +8,7 @@ use crate::database::int_enum::{int_enum_from_sql, int_enum_to_sql};
use crate::errors::ConversionError; use crate::errors::ConversionError;
use crate::utils::caip2::ChainId; use crate::utils::caip2::ChainId;
#[derive(Debug)] #[derive(Debug, PartialEq)]
pub enum InvoiceStatus { pub enum InvoiceStatus {
Open, Open,
Paid, Paid,

59
src/monero/helpers.rs Normal file
View file

@ -0,0 +1,59 @@
use std::str::FromStr;
use monero_rpc::TransferType;
use monero_rpc::monero::Address;
use tokio_postgres::GenericClient;
use uuid::Uuid;
use crate::config::MoneroConfig;
use crate::models::{
invoices::queries::{
get_invoice_by_id,
set_invoice_status,
},
invoices::types::InvoiceStatus,
};
use super::wallet::{
open_monero_wallet,
DEFAULT_ACCOUNT,
MoneroError,
};
pub async fn check_expired_invoice(
config: &MoneroConfig,
db_client: &impl GenericClient,
invoice_id: &Uuid,
) -> Result<(), MoneroError> {
let wallet_client = open_monero_wallet(config).await?;
let invoice = get_invoice_by_id(db_client, invoice_id).await?;
if invoice.chain_id != config.chain_id ||
invoice.invoice_status != InvoiceStatus::Timeout
{
return Err(MoneroError::OtherError("can't process invoice"));
};
let address = Address::from_str(&invoice.payment_address)?;
let address_index = wallet_client.get_address_index(address).await?;
let transfers = wallet_client.incoming_transfers(
TransferType::Available,
Some(DEFAULT_ACCOUNT),
Some(vec![address_index.minor]),
).await?
.transfers
.unwrap_or_default();
if transfers.is_empty() {
log::info!("no incoming transfers");
} else {
for transfer in transfers {
if transfer.subaddr_index != address_index {
return Err(MoneroError::WalletRpcError("unexpected transfer"));
};
log::info!(
"received payment for invoice {}: {}",
invoice.id,
transfer.amount,
);
};
set_invoice_status(db_client, &invoice.id, InvoiceStatus::Paid).await?;
};
Ok(())
}

View file

@ -1,2 +1,3 @@
pub mod helpers;
pub mod subscriptions; pub mod subscriptions;
pub mod wallet; pub mod wallet;

View file

@ -2,7 +2,7 @@ use std::convert::TryInto;
use std::str::FromStr; use std::str::FromStr;
use chrono::{Duration, Utc}; use chrono::{Duration, Utc};
use monero_rpc::{RpcClient, TransferType}; use monero_rpc::TransferType;
use monero_rpc::monero::{Address, Amount}; use monero_rpc::monero::{Address, Amount};
use crate::config::{Instance, MoneroConfig}; use crate::config::{Instance, MoneroConfig};
@ -28,6 +28,7 @@ use crate::models::{
use super::wallet::{ use super::wallet::{
get_single_item, get_single_item,
get_subaddress_balance, get_subaddress_balance,
open_monero_wallet,
send_monero, send_monero,
DEFAULT_ACCOUNT, DEFAULT_ACCOUNT,
MoneroError, MoneroError,
@ -41,12 +42,7 @@ pub async fn check_monero_subscriptions(
db_pool: &Pool, db_pool: &Pool,
) -> Result<(), MoneroError> { ) -> Result<(), MoneroError> {
let db_client = &mut **get_database_client(db_pool).await?; let db_client = &mut **get_database_client(db_pool).await?;
let wallet_client = open_monero_wallet(config).await?;
let wallet_client = RpcClient::new(config.wallet_url.clone()).wallet();
wallet_client.open_wallet(
config.wallet_name.clone(),
config.wallet_password.clone(),
).await?;
// Invoices waiting for payment // Invoices waiting for payment
let mut address_waitlist = vec![]; let mut address_waitlist = vec![];
@ -98,7 +94,11 @@ pub async fn check_monero_subscriptions(
invoice.id, invoice.id,
transfer.amount, transfer.amount,
); );
set_invoice_status(db_client, &invoice.id, InvoiceStatus::Paid).await?; if invoice.invoice_status == InvoiceStatus::Open {
set_invoice_status(db_client, &invoice.id, InvoiceStatus::Paid).await?;
} else {
log::warn!("invoice has already been paid");
};
}; };
}; };

View file

@ -48,14 +48,21 @@ pub async fn create_monero_wallet(
Ok(()) Ok(())
} }
pub async fn create_monero_address( pub async fn open_monero_wallet(
config: &MoneroConfig, config: &MoneroConfig,
) -> Result<Address, MoneroError> { ) -> Result<WalletClient, MoneroError> {
let wallet_client = RpcClient::new(config.wallet_url.clone()).wallet(); let wallet_client = RpcClient::new(config.wallet_url.clone()).wallet();
wallet_client.open_wallet( wallet_client.open_wallet(
config.wallet_name.clone(), config.wallet_name.clone(),
config.wallet_password.clone(), config.wallet_password.clone(),
).await?; ).await?;
Ok(wallet_client)
}
pub async fn create_monero_address(
config: &MoneroConfig,
) -> Result<Address, MoneroError> {
let wallet_client = open_monero_wallet(config).await?;
let (address, address_index) = let (address, address_index) =
wallet_client.create_address(DEFAULT_ACCOUNT, None).await?; wallet_client.create_address(DEFAULT_ACCOUNT, None).await?;
log::info!("created monero address {}/{}", DEFAULT_ACCOUNT, address_index); log::info!("created monero address {}/{}", DEFAULT_ACCOUNT, address_index);