use crate::payment::state_machine::PaymentStateMachine;
use crate::rpc::rest::types::{
error_response, rest_error_failed, rest_error_invalid, success_response,
};
use bytes::Bytes;
use http_body_util::Full;
use hyper::{Method, Response, StatusCode};
use serde_json::{json, Value};
use std::sync::Arc;
use tracing::{debug, error};
use uuid::Uuid;
#[cfg(feature = "ctv")]
pub async fn handle_vault_request(
state_machine: Arc<PaymentStateMachine>,
method: &Method,
path: &str,
body: Option<Value>,
request_id: String,
) -> Response<Full<Bytes>> {
debug!(
"Vault REST request: {} {} (request_id: {})",
method,
path,
&request_id[..8]
);
let path_parts: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
match (method, path_parts.as_slice()) {
(&Method::POST, ["api", "v1", "vaults"]) => {
create_vault(state_machine, body, request_id).await
}
(&Method::POST, ["api", "v1", "vaults", vault_id, "unvault"]) => {
unvault_vault(state_machine, body, vault_id, request_id).await
}
(&Method::POST, ["api", "v1", "vaults", vault_id, "withdraw"]) => {
withdraw_from_vault(state_machine, body, vault_id, request_id).await
}
(&Method::GET, ["api", "v1", "vaults", vault_id]) => {
get_vault_state(state_machine, vault_id, request_id).await
}
_ => error_response(
StatusCode::NOT_FOUND,
"NOT_FOUND",
&format!("Vault endpoint not found: {} {}", method, path),
request_id,
),
}
}
async fn create_vault(
state_machine: Arc<PaymentStateMachine>,
body: Option<Value>,
request_id: String,
) -> Response<Full<Bytes>> {
#[cfg(not(feature = "ctv"))]
{
return error_response(
StatusCode::NOT_IMPLEMENTED,
"NOT_IMPLEMENTED",
"Vaults require CTV feature",
request_id,
);
}
#[cfg(feature = "ctv")]
{
let body = match body {
Some(b) => b,
None => {
return error_response(
StatusCode::BAD_REQUEST,
"BAD_REQUEST",
"Request body required",
request_id,
);
}
};
let vault_engine = match state_machine.vault_engine() {
Some(engine) => engine,
None => {
return error_response(
StatusCode::SERVICE_UNAVAILABLE,
"SERVICE_UNAVAILABLE",
"Vault engine not available",
request_id,
);
}
};
let vault_id = match body["vault_id"].as_str() {
Some(id) => id,
None => {
return error_response(
StatusCode::BAD_REQUEST,
"BAD_REQUEST",
"vault_id required",
request_id,
);
}
};
let deposit_amount = match body["deposit_amount"].as_u64() {
Some(amount) => amount,
None => {
return error_response(
StatusCode::BAD_REQUEST,
"BAD_REQUEST",
"deposit_amount required",
request_id,
);
}
};
let withdrawal_script_hex = match body["withdrawal_script"].as_str() {
Some(script) => script,
None => {
return error_response(
StatusCode::BAD_REQUEST,
"BAD_REQUEST",
"withdrawal_script required",
request_id,
);
}
};
let withdrawal_script = match hex::decode(withdrawal_script_hex) {
Ok(script) => script,
Err(e) => {
return error_response(
StatusCode::BAD_REQUEST,
"BAD_REQUEST",
&rest_error_invalid("withdrawal_script", e),
request_id,
);
}
};
let config = if body["config"].is_object() {
serde_json::from_value(body["config"].clone())
.unwrap_or_else(|_| crate::payment::vault::VaultConfig::default())
} else {
crate::payment::vault::VaultConfig::default()
};
match vault_engine.create_vault(vault_id, deposit_amount, withdrawal_script, config) {
Ok(vault_state) => {
let response_data = json!({
"vault_id": vault_state.vault_id,
"vault_state": serde_json::to_value(&vault_state)
.unwrap_or_else(|_| json!(null)),
});
success_response(response_data, request_id)
}
Err(e) => {
error!("Failed to create vault: {}", e);
error_response(
StatusCode::INTERNAL_SERVER_ERROR,
"VAULT_CREATION_FAILED",
&format!("Failed to create vault: {}", e),
request_id,
)
}
}
}
}
async fn unvault_vault(
state_machine: Arc<PaymentStateMachine>,
body: Option<Value>,
vault_id: &str,
request_id: String,
) -> Response<Full<Bytes>> {
#[cfg(not(feature = "ctv"))]
{
return error_response(
StatusCode::NOT_IMPLEMENTED,
"NOT_IMPLEMENTED",
"Vaults require CTV feature",
request_id,
);
}
#[cfg(feature = "ctv")]
{
use serde_json::json;
let vault_engine = match state_machine.vault_engine() {
Some(engine) => engine,
None => {
return error_response(
StatusCode::INTERNAL_SERVER_ERROR,
"INTERNAL_ERROR",
"Vault engine not available",
request_id,
);
}
};
let vault_state = match vault_engine.get_vault(vault_id) {
Ok(Some(state)) => state,
Ok(None) => {
return error_response(
StatusCode::NOT_FOUND,
"NOT_FOUND",
&format!("Vault {} not found", vault_id),
request_id,
);
}
Err(e) => {
return error_response(
StatusCode::INTERNAL_SERVER_ERROR,
"INTERNAL_ERROR",
&rest_error_failed("get vault state", e),
request_id,
);
}
};
let amount = body
.and_then(|v| v.get("amount").and_then(|a| a.as_u64()))
.unwrap_or(0);
if amount == 0 {
return error_response(
StatusCode::BAD_REQUEST,
"BAD_REQUEST",
"Deposit amount must be greater than 0",
request_id,
);
}
return success_response(
json!({
"vault_id": vault_id,
"amount": amount,
"current_balance": vault_state.deposit_amount,
"message": "Deposit request received (full implementation pending)"
}),
request_id,
);
}
}
async fn withdraw_from_vault(
state_machine: Arc<PaymentStateMachine>,
body: Option<Value>,
vault_id: &str,
request_id: String,
) -> Response<Full<Bytes>> {
#[cfg(not(feature = "ctv"))]
{
return error_response(
StatusCode::NOT_IMPLEMENTED,
"NOT_IMPLEMENTED",
"Vaults require CTV feature",
request_id,
);
}
#[cfg(feature = "ctv")]
{
use serde_json::json;
let vault_engine = match state_machine.vault_engine() {
Some(engine) => engine,
None => {
return error_response(
StatusCode::INTERNAL_SERVER_ERROR,
"INTERNAL_ERROR",
"Vault engine not available",
request_id,
);
}
};
let vault_state = match vault_engine.get_vault(vault_id) {
Ok(Some(state)) => state,
Ok(None) => {
return error_response(
StatusCode::NOT_FOUND,
"NOT_FOUND",
&format!("Vault {} not found", vault_id),
request_id,
);
}
Err(e) => {
return error_response(
StatusCode::INTERNAL_SERVER_ERROR,
"INTERNAL_ERROR",
&rest_error_failed("get vault state", e),
request_id,
);
}
};
let amount = body
.and_then(|v| v.get("amount").and_then(|a| a.as_u64()))
.unwrap_or(0);
if amount == 0 {
return error_response(
StatusCode::BAD_REQUEST,
"BAD_REQUEST",
"Withdrawal amount must be greater than 0",
request_id,
);
}
let available = vault_state.deposit_amount;
if amount > available {
return error_response(
StatusCode::BAD_REQUEST,
"BAD_REQUEST",
&format!("Insufficient balance: {} > {}", amount, available),
request_id,
);
}
return success_response(
json!({
"vault_id": vault_id,
"amount": amount,
"remaining_balance": available.saturating_sub(amount),
"message": "Withdrawal request received (full implementation pending)"
}),
request_id,
);
}
}
async fn get_vault_state(
state_machine: Arc<PaymentStateMachine>,
vault_id: &str,
request_id: String,
) -> Response<Full<Bytes>> {
#[cfg(not(feature = "ctv"))]
{
return error_response(
StatusCode::NOT_IMPLEMENTED,
"NOT_IMPLEMENTED",
"Vaults require CTV feature",
request_id,
);
}
#[cfg(feature = "ctv")]
{
match state_machine.vault_engine() {
Some(vault_engine) => match vault_engine.get_vault(vault_id) {
Ok(Some(vault_state)) => {
let response_data = json!({
"vault_id": vault_state.vault_id,
"vault_state": serde_json::to_value(&vault_state)
.unwrap_or_else(|_| json!(null)),
});
success_response(response_data, request_id)
}
Ok(None) => error_response(
StatusCode::NOT_FOUND,
"NOT_FOUND",
&format!("Vault {} not found", vault_id),
request_id,
),
Err(e) => error_response(
StatusCode::INTERNAL_SERVER_ERROR,
"VAULT_LOAD_FAILED",
&rest_error_failed("load vault", e),
request_id,
),
},
None => error_response(
StatusCode::SERVICE_UNAVAILABLE,
"SERVICE_UNAVAILABLE",
"Vault engine not available",
request_id,
),
}
}
}