use crate::payment::processor::PaymentError;
#[cfg(feature = "bip70-http")]
use crate::payment::processor::PaymentProcessor;
use bytes::Bytes;
use http_body_util::Limited;
use http_body_util::{BodyExt, Full};
use hyper::body::Incoming;
use hyper::http::StatusCode;
use hyper::{Method, Request, Response};
use hyper_util::rt::TokioIo;
use std::sync::Arc;
use tracing::{debug, error, info, warn};
const MAX_BODY_SIZE: usize = crate::rpc::server::DEFAULT_MAX_REQUEST_SIZE;
#[cfg(feature = "bip70-http")]
pub async fn handle_create_payment_request(
processor: Arc<PaymentProcessor>,
req: Request<Incoming>,
) -> Result<Response<Full<Bytes>>, PaymentError> {
use blvm_protocol::payment::{PaymentOutput, PaymentRequest};
if req.method() != Method::POST {
return Ok(Response::builder()
.status(StatusCode::METHOD_NOT_ALLOWED)
.body(Full::new(Bytes::from("Method not allowed")))
.unwrap());
}
let (_, body) = req.into_parts();
let limited = Limited::new(body, MAX_BODY_SIZE);
let body = limited.collect().await.map_err(|e| {
PaymentError::ProcessingError(format!("Failed to read request body: {}", e))
})?;
let body_bytes = body.to_bytes();
let params: serde_json::Value = serde_json::from_slice(&body_bytes).map_err(|e| {
PaymentError::ProcessingError(format!("Failed to parse request body: {}", e))
})?;
let outputs: Vec<PaymentOutput> = params
.get("outputs")
.and_then(|v| serde_json::from_value(v.clone()).ok())
.ok_or_else(|| PaymentError::ProcessingError("Missing 'outputs' field".to_string()))?;
const MAX_MERCHANT_DATA_HEX_LEN: usize = 512;
const MAX_MERCHANT_DATA_ARRAY_LEN: usize = 256;
let merchant_data = params.get("merchant_data").and_then(|v| {
if v.is_string() {
let s = v.as_str().unwrap();
if s.len() > MAX_MERCHANT_DATA_HEX_LEN {
return None;
}
Some(hex::decode(s).ok()?)
} else if v.is_array() {
let arr = v.as_array().unwrap();
if arr.len() > MAX_MERCHANT_DATA_ARRAY_LEN {
return None;
}
Some(
arr.iter()
.filter_map(|n| n.as_u64().filter(|&u| u <= 255).map(|u| u as u8))
.collect(),
)
} else {
None
}
});
let payment_request = processor
.create_payment_request(outputs, merchant_data, None)
.await?;
let serialized = bincode::serialize(&payment_request).map_err(|e| {
PaymentError::ProcessingError(format!("Failed to serialize payment request: {}", e))
})?;
Ok(Response::builder()
.status(StatusCode::OK)
.header("Content-Type", "application/bitcoin-paymentrequest")
.body(Full::new(Bytes::from(serialized)))
.unwrap())
}
#[cfg(feature = "bip70-http")]
pub async fn handle_get_payment_request(
processor: Arc<PaymentProcessor>,
req: Request<Incoming>,
) -> Result<Response<Full<Bytes>>, PaymentError> {
if req.method() != Method::GET {
return Ok(Response::builder()
.status(StatusCode::METHOD_NOT_ALLOWED)
.body(Full::new(Bytes::from("Method not allowed")))
.unwrap());
}
let path = req.uri().path();
let payment_id = path
.strip_prefix("/api/v1/payment/request/")
.ok_or_else(|| PaymentError::ProcessingError("Invalid path".to_string()))?;
let payment_request = processor.get_payment_request(payment_id).await?;
let serialized = bincode::serialize(&payment_request).map_err(|e| {
PaymentError::ProcessingError(format!("Failed to serialize payment request: {}", e))
})?;
Ok(Response::builder()
.status(StatusCode::OK)
.header("Content-Type", "application/bitcoin-paymentrequest")
.body(Full::new(Bytes::from(serialized)))
.unwrap())
}
#[cfg(feature = "bip70-http")]
pub async fn handle_submit_payment(
processor: Arc<PaymentProcessor>,
req: Request<Incoming>,
) -> Result<Response<Full<Bytes>>, PaymentError> {
use blvm_protocol::payment::Payment;
if req.method() != Method::POST {
return Ok(Response::builder()
.status(StatusCode::METHOD_NOT_ALLOWED)
.body(Full::new(Bytes::from("Method not allowed")))
.unwrap());
}
let payment_id = req
.uri()
.query()
.and_then(|q| {
q.split('&').find_map(|p| {
if p.starts_with("payment_id=") {
Some(p.strip_prefix("payment_id=").unwrap().to_string())
} else {
None
}
})
})
.ok_or_else(|| PaymentError::ProcessingError("Missing payment_id".to_string()))?;
let (_, body) = req.into_parts();
let limited = Limited::new(body, MAX_BODY_SIZE);
let body = limited.collect().await.map_err(|e| {
PaymentError::ProcessingError(format!("Failed to read request body: {}", e))
})?;
let body_bytes = body.to_bytes();
let payment: Payment = bincode::deserialize(&body_bytes)
.map_err(|e| PaymentError::ProcessingError(format!("Failed to parse payment: {}", e)))?;
let ack = processor.process_payment(payment, payment_id, None).await?;
let serialized = bincode::serialize(&ack).map_err(|e| {
PaymentError::ProcessingError(format!("Failed to serialize payment ACK: {}", e))
})?;
Ok(Response::builder()
.status(StatusCode::OK)
.header("Content-Type", "application/bitcoin-paymentack")
.body(Full::new(Bytes::from(serialized)))
.unwrap())
}
#[cfg(feature = "bip70-http")]
pub async fn handle_payment_routes(
processor: Arc<PaymentProcessor>,
req: Request<Incoming>,
) -> Result<Response<Full<Bytes>>, PaymentError> {
let path = req.uri().path();
let method = req.method().clone();
match (method, path) {
(Method::POST, "/api/v1/payment/request") => {
handle_create_payment_request(processor, req).await
}
(Method::GET, path) if path.starts_with("/api/v1/payment/request/") => {
handle_get_payment_request(processor, req).await
}
(Method::POST, "/api/v1/payment") => handle_submit_payment(processor, req).await,
_ => Ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Full::new(Bytes::from("Not found")))
.unwrap()),
}
}
#[cfg(not(feature = "bip70-http"))]
pub async fn handle_payment_routes(
_processor: Arc<PaymentProcessor>,
_req: Request<Incoming>,
) -> Result<Response<Full<Bytes>>, PaymentError> {
use http_body_util::Full;
Ok(Response::builder()
.status(StatusCode::NOT_IMPLEMENTED)
.body(Full::new(Bytes::from(
"HTTP BIP70 not enabled. Compile with --features bip70-http",
)))
.unwrap())
}