closes https://github.com/LNVPS/api/issues/31
This commit is contained in:
93
Cargo.lock
generated
93
Cargo.lock
generated
@ -27,6 +27,17 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aes"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cipher",
|
||||||
|
"cpufeatures",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ahash"
|
name = "ahash"
|
||||||
version = "0.8.11"
|
version = "0.8.11"
|
||||||
@ -309,6 +320,16 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
|
checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base58ck"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f"
|
||||||
|
dependencies = [
|
||||||
|
"bitcoin-internals 0.3.0",
|
||||||
|
"bitcoin_hashes 0.14.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.21.7"
|
version = "0.21.7"
|
||||||
@ -350,25 +371,62 @@ dependencies = [
|
|||||||
"unicode-normalization",
|
"unicode-normalization",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitcoin"
|
||||||
|
version = "0.32.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce6bc65742dea50536e35ad42492b234c27904a27f0abdcbce605015cb4ea026"
|
||||||
|
dependencies = [
|
||||||
|
"base58ck",
|
||||||
|
"bech32",
|
||||||
|
"bitcoin-internals 0.3.0",
|
||||||
|
"bitcoin-io",
|
||||||
|
"bitcoin-units",
|
||||||
|
"bitcoin_hashes 0.14.0",
|
||||||
|
"hex-conservative 0.2.1",
|
||||||
|
"hex_lit",
|
||||||
|
"secp256k1",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitcoin-internals"
|
name = "bitcoin-internals"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb"
|
checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitcoin-internals"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitcoin-io"
|
name = "bitcoin-io"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf"
|
checksum = "0b47c4ab7a93edb0c7198c5535ed9b52b63095f4e9b45279c6736cec4b856baf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitcoin-units"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2"
|
||||||
|
dependencies = [
|
||||||
|
"bitcoin-internals 0.3.0",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitcoin_hashes"
|
name = "bitcoin_hashes"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b"
|
checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitcoin-internals",
|
"bitcoin-internals 0.2.0",
|
||||||
"hex-conservative 0.1.2",
|
"hex-conservative 0.1.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -920,9 +978,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "email_address"
|
name = "email_address"
|
||||||
version = "0.2.9"
|
version = "0.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449"
|
checksum = "c1019fa28f600f5b581b7a603d515c3f1635da041ca211b5055804788673abfe"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
@ -1394,6 +1455,12 @@ dependencies = [
|
|||||||
"arrayvec",
|
"arrayvec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hex_lit"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hkdf"
|
name = "hkdf"
|
||||||
version = "0.12.4"
|
version = "0.12.4"
|
||||||
@ -2037,6 +2104,24 @@ version = "0.7.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
|
checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lnurl-rs"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "41eacdd87b675792f7752f3dd0937a00241a504c3956c47f72986490662e1db4"
|
||||||
|
dependencies = [
|
||||||
|
"aes",
|
||||||
|
"anyhow",
|
||||||
|
"base64 0.22.1",
|
||||||
|
"bech32",
|
||||||
|
"bitcoin",
|
||||||
|
"cbc",
|
||||||
|
"email_address",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lnvps_api"
|
name = "lnvps_api"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -2054,6 +2139,7 @@ dependencies = [
|
|||||||
"ipnetwork",
|
"ipnetwork",
|
||||||
"isocountry",
|
"isocountry",
|
||||||
"lettre",
|
"lettre",
|
||||||
|
"lnurl-rs",
|
||||||
"lnvps_common",
|
"lnvps_common",
|
||||||
"lnvps_db",
|
"lnvps_db",
|
||||||
"log",
|
"log",
|
||||||
@ -3491,6 +3577,7 @@ version = "0.29.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113"
|
checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bitcoin_hashes 0.14.0",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"secp256k1-sys",
|
"secp256k1-sys",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -56,6 +56,7 @@ ssh-key = "0.6.7"
|
|||||||
lettre = { version = "0.11.10", features = ["tokio1-native-tls"] }
|
lettre = { version = "0.11.10", features = ["tokio1-native-tls"] }
|
||||||
ws = { package = "rocket_ws", version = "0.1.1" }
|
ws = { package = "rocket_ws", version = "0.1.1" }
|
||||||
native-tls = "0.2.12"
|
native-tls = "0.2.12"
|
||||||
|
lnurl-rs = { version = "0.9.0", default-features = false }
|
||||||
|
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
isocountry = "0.3.2"
|
isocountry = "0.3.2"
|
||||||
|
@ -149,7 +149,7 @@ impl ApiTemplatesResponse {
|
|||||||
CurrencyAmount::from_f32(template.cost_plan.currency, template.cost_plan.amount);
|
CurrencyAmount::from_f32(template.cost_plan.currency, template.cost_plan.amount);
|
||||||
for alt_price in alt_prices(&rates, list_price) {
|
for alt_price in alt_prices(&rates, list_price) {
|
||||||
template.cost_plan.other_price.push(ApiPrice {
|
template.cost_plan.other_price.push(ApiPrice {
|
||||||
currency: alt_price.0,
|
currency: alt_price.currency(),
|
||||||
amount: alt_price.value_f32(),
|
amount: alt_price.value_f32(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -255,7 +255,7 @@ pub struct ApiPrice {
|
|||||||
impl From<CurrencyAmount> for ApiPrice {
|
impl From<CurrencyAmount> for ApiPrice {
|
||||||
fn from(value: CurrencyAmount) -> Self {
|
fn from(value: CurrencyAmount) -> Self {
|
||||||
Self {
|
Self {
|
||||||
currency: value.0,
|
currency: value.currency(),
|
||||||
amount: value.value_f32(),
|
amount: value.value_f32(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ use crate::api::model::{
|
|||||||
ApiVmIpAssignment, ApiVmOsImage, ApiVmPayment, ApiVmStatus, ApiVmTemplate, CreateSshKey,
|
ApiVmIpAssignment, ApiVmOsImage, ApiVmPayment, ApiVmStatus, ApiVmTemplate, CreateSshKey,
|
||||||
CreateVmRequest, VMPatchRequest,
|
CreateVmRequest, VMPatchRequest,
|
||||||
};
|
};
|
||||||
use crate::exchange::{Currency, ExchangeRateService};
|
use crate::exchange::{Currency, CurrencyAmount, ExchangeRateService};
|
||||||
use crate::host::{get_host_client, FullVmInfo, TimeSeries, TimeSeriesData};
|
use crate::host::{get_host_client, FullVmInfo, TimeSeries, TimeSeriesData};
|
||||||
use crate::nip98::Nip98Auth;
|
use crate::nip98::Nip98Auth;
|
||||||
use crate::provisioner::{HostCapacityService, LNVpsProvisioner, PricingEngine};
|
use crate::provisioner::{HostCapacityService, LNVpsProvisioner, PricingEngine};
|
||||||
@ -15,11 +15,14 @@ use anyhow::{bail, Result};
|
|||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
use futures::{SinkExt, StreamExt};
|
use futures::{SinkExt, StreamExt};
|
||||||
use isocountry::CountryCode;
|
use isocountry::CountryCode;
|
||||||
|
use lnurl::pay::{LnURLPayInvoice, PayResponse};
|
||||||
|
use lnurl::Tag;
|
||||||
use lnvps_db::{
|
use lnvps_db::{
|
||||||
IpRange, LNVpsDb, PaymentMethod, VmCustomPricing, VmCustomPricingDisk, VmCustomTemplate,
|
IpRange, LNVpsDb, PaymentMethod, VmCustomPricing, VmCustomPricingDisk, VmCustomTemplate,
|
||||||
};
|
};
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use nostr::util::hex;
|
use nostr::util::hex;
|
||||||
|
use nostr::Url;
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use rocket::{get, patch, post, routes, Responder, Route, State};
|
use rocket::{get, patch, post, routes, Responder, Route, State};
|
||||||
use rocket_okapi::gen::OpenApiGenerator;
|
use rocket_okapi::gen::OpenApiGenerator;
|
||||||
@ -64,7 +67,11 @@ pub fn routes() -> Vec<Route> {
|
|||||||
api_routes.append(&mut super::nostr_domain::routes());
|
api_routes.append(&mut super::nostr_domain::routes());
|
||||||
routes.append(&mut api_routes);
|
routes.append(&mut api_routes);
|
||||||
|
|
||||||
routes.append(&mut routes![v1_terminal_proxy]);
|
routes.append(&mut routes![
|
||||||
|
v1_terminal_proxy,
|
||||||
|
v1_lnurlp,
|
||||||
|
v1_renew_vm_lnurlp
|
||||||
|
]);
|
||||||
|
|
||||||
routes
|
routes
|
||||||
}
|
}
|
||||||
@ -540,6 +547,57 @@ async fn v1_renew_vm(
|
|||||||
ApiData::ok(rsp.into())
|
ApiData::ok(rsp.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extend a VM by LNURL payment
|
||||||
|
#[get("/api/v1/vm/<id>/renew-lnurlp?<amount>")]
|
||||||
|
async fn v1_renew_vm_lnurlp(
|
||||||
|
provisioner: &State<Arc<LNVpsProvisioner>>,
|
||||||
|
id: u64,
|
||||||
|
amount: u64,
|
||||||
|
) -> Result<Json<LnURLPayInvoice>, &'static str> {
|
||||||
|
if amount < 1000 {
|
||||||
|
return Err("Amount must be greater than 1000");
|
||||||
|
}
|
||||||
|
|
||||||
|
let rsp = provisioner
|
||||||
|
.renew_amount(
|
||||||
|
id,
|
||||||
|
CurrencyAmount::millisats(amount),
|
||||||
|
PaymentMethod::Lightning,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|_| "Error generating invoice")?;
|
||||||
|
|
||||||
|
// external_data is pr for lightning payment method
|
||||||
|
Ok(Json(LnURLPayInvoice::new(rsp.external_data)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// LNURL ad-hoc extend vm
|
||||||
|
#[get("/.well-known/lnurlp/<id>")]
|
||||||
|
async fn v1_lnurlp(
|
||||||
|
db: &State<Arc<dyn LNVpsDb>>,
|
||||||
|
settings: &State<Settings>,
|
||||||
|
id: u64,
|
||||||
|
) -> Result<Json<PayResponse>, &'static str> {
|
||||||
|
db.get_vm(id).await.map_err(|_e| "VM not found")?;
|
||||||
|
|
||||||
|
let meta = vec![vec!["text/plain".to_string(), format!("Extend VM {}", id)]];
|
||||||
|
let rsp = PayResponse {
|
||||||
|
callback: Url::parse(&settings.public_url)
|
||||||
|
.map_err(|_| "Invalid public url")?
|
||||||
|
.join(&format!("/api/v1/vm/{}/renew-lnurlp", id))
|
||||||
|
.map_err(|_| "Could not get callback url")?
|
||||||
|
.to_string(),
|
||||||
|
max_sendable: 1_000_000_000,
|
||||||
|
min_sendable: 1_000, // TODO: calc min by using 1s extend time
|
||||||
|
tag: Tag::PayRequest,
|
||||||
|
metadata: serde_json::to_string(&meta).map_err(|_e| "Failed to serialize metadata")?,
|
||||||
|
comment_allowed: None,
|
||||||
|
allows_nostr: None,
|
||||||
|
nostr_pubkey: None,
|
||||||
|
};
|
||||||
|
Ok(Json(rsp))
|
||||||
|
}
|
||||||
|
|
||||||
/// Start a VM
|
/// Start a VM
|
||||||
#[openapi(tag = "VM")]
|
#[openapi(tag = "VM")]
|
||||||
#[patch("/api/v1/vm/<id>/start")]
|
#[patch("/api/v1/vm/<id>/start")]
|
||||||
|
@ -75,14 +75,19 @@ impl Display for Ticker {
|
|||||||
pub struct TickerRate(pub Ticker, pub f32);
|
pub struct TickerRate(pub Ticker, pub f32);
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct CurrencyAmount(pub Currency, u64);
|
pub struct CurrencyAmount(Currency, u64);
|
||||||
|
|
||||||
impl CurrencyAmount {
|
impl CurrencyAmount {
|
||||||
const MILLI_SATS: f64 = 1.0e11;
|
const MILLI_SATS: f64 = 1.0e11;
|
||||||
|
|
||||||
|
pub fn millisats(amount: u64) -> Self {
|
||||||
|
CurrencyAmount(Currency::BTC, amount)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_u64(currency: Currency, amount: u64) -> Self {
|
pub fn from_u64(currency: Currency, amount: u64) -> Self {
|
||||||
CurrencyAmount(currency, amount)
|
CurrencyAmount(currency, amount)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_f32(currency: Currency, amount: f32) -> Self {
|
pub fn from_f32(currency: Currency, amount: f32) -> Self {
|
||||||
CurrencyAmount(
|
CurrencyAmount(
|
||||||
currency,
|
currency,
|
||||||
@ -103,6 +108,10 @@ impl CurrencyAmount {
|
|||||||
_ => self.1 as f32 / 100.0,
|
_ => self.1 as f32 / 100.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn currency(&self) -> Currency {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TickerRate {
|
impl TickerRate {
|
||||||
|
@ -89,8 +89,8 @@ impl RevolutApi {
|
|||||||
.post(
|
.post(
|
||||||
"/api/orders",
|
"/api/orders",
|
||||||
CreateOrderRequest {
|
CreateOrderRequest {
|
||||||
currency: amount.0.to_string(),
|
currency: amount.currency().to_string(),
|
||||||
amount: match amount.0 {
|
amount: match amount.currency() {
|
||||||
Currency::BTC => bail!("Bitcoin amount not allowed for fiat payments"),
|
Currency::BTC => bail!("Bitcoin amount not allowed for fiat payments"),
|
||||||
_ => amount.value(),
|
_ => amount.value(),
|
||||||
},
|
},
|
||||||
|
@ -483,8 +483,23 @@ impl LNVpsProvisioner {
|
|||||||
/// Create a renewal payment
|
/// Create a renewal payment
|
||||||
pub async fn renew(&self, vm_id: u64, method: PaymentMethod) -> Result<VmPayment> {
|
pub async fn renew(&self, vm_id: u64, method: PaymentMethod) -> Result<VmPayment> {
|
||||||
let pe = PricingEngine::new(self.db.clone(), self.rates.clone(), self.tax_rates.clone());
|
let pe = PricingEngine::new(self.db.clone(), self.rates.clone(), self.tax_rates.clone());
|
||||||
|
|
||||||
let price = pe.get_vm_cost(vm_id, method).await?;
|
let price = pe.get_vm_cost(vm_id, method).await?;
|
||||||
|
self.price_to_payment(vm_id, method, price).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renew a VM using a specific amount
|
||||||
|
pub async fn renew_amount(&self, vm_id: u64, amount: CurrencyAmount, method: PaymentMethod) -> Result<VmPayment> {
|
||||||
|
let pe = PricingEngine::new(self.db.clone(), self.rates.clone(), self.tax_rates.clone());
|
||||||
|
let price = pe.get_cost_by_amount(vm_id, amount, method).await?;
|
||||||
|
self.price_to_payment(vm_id, method, price).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn price_to_payment(
|
||||||
|
&self,
|
||||||
|
vm_id: u64,
|
||||||
|
method: PaymentMethod,
|
||||||
|
price: CostResult,
|
||||||
|
) -> Result<VmPayment> {
|
||||||
match price {
|
match price {
|
||||||
CostResult::Existing(p) => Ok(p),
|
CostResult::Existing(p) => Ok(p),
|
||||||
CostResult::New {
|
CostResult::New {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use crate::exchange::{Currency, CurrencyAmount, ExchangeRateService, Ticker, TickerRate};
|
use crate::exchange::{Currency, CurrencyAmount, ExchangeRateService, Ticker, TickerRate};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, ensure, Result};
|
||||||
use chrono::{DateTime, Days, Months, TimeDelta, Utc};
|
use chrono::{DateTime, Days, Months, TimeDelta, Utc};
|
||||||
use ipnetwork::IpNetwork;
|
use ipnetwork::IpNetwork;
|
||||||
use isocountry::CountryCode;
|
use isocountry::CountryCode;
|
||||||
@ -34,6 +34,49 @@ impl PricingEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get amount of time a certain currency amount will extend a vm in seconds
|
||||||
|
pub async fn get_cost_by_amount(
|
||||||
|
&self,
|
||||||
|
vm_id: u64,
|
||||||
|
input: CurrencyAmount,
|
||||||
|
method: PaymentMethod,
|
||||||
|
) -> Result<CostResult> {
|
||||||
|
let vm = self.db.get_vm(vm_id).await?;
|
||||||
|
|
||||||
|
let cost = if vm.template_id.is_some() {
|
||||||
|
self.get_template_vm_cost(&vm, method).await?
|
||||||
|
} else {
|
||||||
|
self.get_custom_vm_cost(&vm, method).await?
|
||||||
|
};
|
||||||
|
|
||||||
|
match cost {
|
||||||
|
CostResult::Existing(_) => bail!("Invalid response"),
|
||||||
|
CostResult::New {
|
||||||
|
currency,
|
||||||
|
amount,
|
||||||
|
rate,
|
||||||
|
time_value,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
ensure!(currency == input.currency(), "Invalid currency");
|
||||||
|
|
||||||
|
// scale cost
|
||||||
|
let scale = input.value() as f64 / amount as f64;
|
||||||
|
let new_time = (time_value as f64 * scale).floor() as u64;
|
||||||
|
ensure!(new_time > 0, "Extend time is less than 1 second");
|
||||||
|
|
||||||
|
Ok(CostResult::New {
|
||||||
|
amount: input.value(),
|
||||||
|
currency,
|
||||||
|
time_value: new_time,
|
||||||
|
new_expiry: vm.expires.add(TimeDelta::seconds(new_time as i64)),
|
||||||
|
rate,
|
||||||
|
tax: self.get_tax_for_user(vm.user_id, input.value()).await?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get VM cost (for renewal)
|
/// Get VM cost (for renewal)
|
||||||
pub async fn get_vm_cost(&self, vm_id: u64, method: PaymentMethod) -> Result<CostResult> {
|
pub async fn get_vm_cost(&self, vm_id: u64, method: PaymentMethod) -> Result<CostResult> {
|
||||||
let vm = self.db.get_vm(vm_id).await?;
|
let vm = self.db.get_vm(vm_id).await?;
|
||||||
@ -160,13 +203,13 @@ impl PricingEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn get_msats_amount(&self, amount: CurrencyAmount) -> Result<(u64, f32)> {
|
async fn get_msats_amount(&self, amount: CurrencyAmount) -> Result<(u64, f32)> {
|
||||||
let rate = self.get_ticker(amount.0).await?;
|
let rate = self.get_ticker(amount.currency()).await?;
|
||||||
let cost_btc = amount.value_f32() / rate.1;
|
let cost_btc = amount.value_f32() / rate.1;
|
||||||
let cost_msats = (cost_btc as f64 * crate::BTC_SATS) as u64 * 1000;
|
let cost_msats = (cost_btc as f64 * crate::BTC_SATS) as u64 * 1000;
|
||||||
Ok((cost_msats, rate.1))
|
Ok((cost_msats, rate.1))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_template_expire(vm: &Vm, cost_plan: &VmCostPlan) -> u64 {
|
pub fn next_template_expire(vm: &Vm, cost_plan: &VmCostPlan) -> u64 {
|
||||||
let next_expire = match cost_plan.interval_type {
|
let next_expire = match cost_plan.interval_type {
|
||||||
VmCostPlanIntervalType::Day => vm.expires.add(Days::new(cost_plan.interval_amount)),
|
VmCostPlanIntervalType::Day => vm.expires.add(Days::new(cost_plan.interval_amount)),
|
||||||
VmCostPlanIntervalType::Month => vm
|
VmCostPlanIntervalType::Month => vm
|
||||||
@ -209,7 +252,7 @@ impl PricingEngine {
|
|||||||
list_price: CurrencyAmount,
|
list_price: CurrencyAmount,
|
||||||
method: PaymentMethod,
|
method: PaymentMethod,
|
||||||
) -> Result<(Currency, u64, f32)> {
|
) -> Result<(Currency, u64, f32)> {
|
||||||
Ok(match (list_price.0, method) {
|
Ok(match (list_price.currency(), method) {
|
||||||
(c, PaymentMethod::Lightning) if c != Currency::BTC => {
|
(c, PaymentMethod::Lightning) if c != Currency::BTC => {
|
||||||
let new_price = self.get_msats_amount(list_price).await?;
|
let new_price = self.get_msats_amount(list_price).await?;
|
||||||
(Currency::BTC, new_price.0, new_price.1)
|
(Currency::BTC, new_price.0, new_price.1)
|
||||||
@ -400,6 +443,25 @@ mod tests {
|
|||||||
_ => bail!("??"),
|
_ => bail!("??"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// from amount
|
||||||
|
let price = pe
|
||||||
|
.get_cost_by_amount(1, CurrencyAmount::millisats(1000), PaymentMethod::Lightning)
|
||||||
|
.await?;
|
||||||
|
// full month price in msats
|
||||||
|
let mo_price = (plan.amount / MOCK_RATE * 1.0e11) as u64;
|
||||||
|
let time_scale = 1000f64 / mo_price as f64;
|
||||||
|
let vm = db.get_vm(1).await?;
|
||||||
|
let next_expire = PricingEngine::next_template_expire(&vm, &plan);
|
||||||
|
match price {
|
||||||
|
CostResult::New { amount, time_value, tax, .. } => {
|
||||||
|
let expect_time = (next_expire as f64 * time_scale) as u64;
|
||||||
|
assert_eq!(expect_time, time_value);
|
||||||
|
assert_eq!(0, tax);
|
||||||
|
assert_eq!(amount, 1000);
|
||||||
|
}
|
||||||
|
_ => bail!("??"),
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user