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
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::ResetSubscriptions(cmd) => cmd.execute(&config, db_client).await.unwrap(),
SubCommand::CreateMoneroWallet(cmd) => cmd.execute(&config).await.unwrap(),
SubCommand::CheckExpiredInvoice(cmd) => cmd.execute(&config, db_client).await.unwrap(),
_ => panic!(),
};
},

View file

@ -29,7 +29,10 @@ use crate::models::users::queries::{
get_user_by_id,
set_user_password,
};
use crate::monero::wallet::create_monero_wallet;
use crate::monero::{
helpers::check_expired_invoice,
wallet::create_monero_wallet,
};
use crate::utils::{
crypto_rsa::{
generate_rsa_key,
@ -64,6 +67,7 @@ pub enum SubCommand {
UpdateCurrentBlock(UpdateCurrentBlock),
ResetSubscriptions(ResetSubscriptions),
CreateMoneroWallet(CreateMoneroWallet),
CheckExpiredInvoice(CheckExpiredInvoice),
}
/// Generate RSA private key
@ -404,3 +408,27 @@ impl CreateMoneroWallet {
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.payment_address, payment_address);
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::utils::caip2::ChainId;
#[derive(Debug)]
#[derive(Debug, PartialEq)]
pub enum InvoiceStatus {
Open,
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 wallet;

View file

@ -2,7 +2,7 @@ use std::convert::TryInto;
use std::str::FromStr;
use chrono::{Duration, Utc};
use monero_rpc::{RpcClient, TransferType};
use monero_rpc::TransferType;
use monero_rpc::monero::{Address, Amount};
use crate::config::{Instance, MoneroConfig};
@ -28,6 +28,7 @@ use crate::models::{
use super::wallet::{
get_single_item,
get_subaddress_balance,
open_monero_wallet,
send_monero,
DEFAULT_ACCOUNT,
MoneroError,
@ -41,12 +42,7 @@ pub async fn check_monero_subscriptions(
db_pool: &Pool,
) -> Result<(), MoneroError> {
let db_client = &mut **get_database_client(db_pool).await?;
let wallet_client = RpcClient::new(config.wallet_url.clone()).wallet();
wallet_client.open_wallet(
config.wallet_name.clone(),
config.wallet_password.clone(),
).await?;
let wallet_client = open_monero_wallet(config).await?;
// Invoices waiting for payment
let mut address_waitlist = vec![];
@ -98,7 +94,11 @@ pub async fn check_monero_subscriptions(
invoice.id,
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(())
}
pub async fn create_monero_address(
pub async fn open_monero_wallet(
config: &MoneroConfig,
) -> Result<Address, MoneroError> {
) -> Result<WalletClient, MoneroError> {
let wallet_client = RpcClient::new(config.wallet_url.clone()).wallet();
wallet_client.open_wallet(
config.wallet_name.clone(),
config.wallet_password.clone(),
).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) =
wallet_client.create_address(DEFAULT_ACCOUNT, None).await?;
log::info!("created monero address {}/{}", DEFAULT_ACCOUNT, address_index);