use crate::config::PaymentConfig;
use crate::module::registry::client::ModuleRegistry;
use blvm_protocol::payment::{
Payment, PaymentACK, PaymentOutput, PaymentProtocolServer, PaymentRequest,
};
use serde_json::json;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use tracing::{debug, info, warn};
fn address_to_script_pubkey(
address: &blvm_protocol::address::BitcoinAddress,
) -> Result<Vec<u8>, PaymentError> {
match (address.witness_version, address.witness_program.len()) {
(0, 20) => {
let mut script = vec![0x00]; script.extend_from_slice(&address.witness_program);
Ok(script)
}
(0, 32) => {
let mut script = vec![0x00]; script.extend_from_slice(&address.witness_program);
Ok(script)
}
(1, 32) => {
let mut script = vec![0x51]; script.extend_from_slice(&address.witness_program);
Ok(script)
}
_ => Err(PaymentError::ProcessingError(format!(
"Unsupported address type: witness_version={}, program_len={}",
address.witness_version,
address.witness_program.len()
))),
}
}
#[derive(Debug, thiserror::Error)]
pub enum PaymentError {
#[error("Payment request not found: {0}")]
RequestNotFound(String),
#[error("Payment validation failed: {0}")]
ValidationFailed(String),
#[error("Feature not enabled: {0}")]
FeatureNotEnabled(String),
#[error("No transport enabled")]
NoTransportEnabled,
#[error("Payment processing error: {0}")]
ProcessingError(String),
#[error("Covenant verification failed: {0}")]
CovenantVerificationFailed(String),
}
pub struct PaymentProcessor {
payment_store: Arc<Mutex<HashMap<String, PaymentRequest>>>,
module_registry: Option<Arc<ModuleRegistry>>,
module_encryption: Option<Arc<crate::module::encryption::ModuleEncryption>>,
modules_dir: Option<std::path::PathBuf>,
config: PaymentConfig,
}
impl PaymentProcessor {
pub fn new(config: PaymentConfig) -> Result<Self, PaymentError> {
#[cfg(not(feature = "bip70-http"))]
if config.http_enabled {
return Err(PaymentError::FeatureNotEnabled(
"HTTP BIP70 requires --features bip70-http".to_string(),
));
}
#[cfg(not(feature = "rest-api"))]
if config.http_enabled {
return Err(PaymentError::FeatureNotEnabled(
"HTTP BIP70 requires --features rest-api".to_string(),
));
}
if !config.p2p_enabled && !config.http_enabled {
return Err(PaymentError::NoTransportEnabled);
}
info!(
"Payment processor initialized: P2P={}, HTTP={}",
config.p2p_enabled, config.http_enabled
);
Ok(Self {
payment_store: Arc::new(Mutex::new(HashMap::new())),
module_registry: None,
module_encryption: None,
modules_dir: None,
config,
})
}
pub fn with_module_registry(mut self, registry: Arc<ModuleRegistry>) -> Self {
self.module_registry = Some(registry);
self
}
pub fn with_module_encryption(
mut self,
encryption: Arc<crate::module::encryption::ModuleEncryption>,
) -> Self {
self.module_encryption = Some(encryption);
self
}
pub fn with_modules_dir(mut self, modules_dir: std::path::PathBuf) -> Self {
self.modules_dir = Some(modules_dir);
self
}
#[allow(dead_code)]
fn get_module_registry(&self) -> Option<&Arc<ModuleRegistry>> {
self.module_registry.as_ref()
}
fn generate_payment_id(request: &PaymentRequest) -> String {
use sha2::{Digest, Sha256};
let serialized = bincode::serialize(request).unwrap_or_default();
let hash = Sha256::digest(&serialized);
hex::encode(&hash[..16]) }
pub async fn create_payment_request(
&self,
outputs: Vec<PaymentOutput>,
merchant_data: Option<Vec<u8>>,
merchant_key: Option<&secp256k1::SecretKey>,
) -> Result<PaymentRequest, PaymentError> {
let timestamp = crate::utils::current_timestamp();
let network_str = match self.config.network.as_deref() {
Some("testnet") | Some("testnet3") => "testnet",
Some("regtest") => "regtest",
Some("signet") => "signet",
_ => "mainnet", };
let mut payment_request = PaymentRequest::new(network_str.to_string(), outputs, timestamp);
if let Some(data) = merchant_data {
payment_request.payment_details.merchant_data = Some(data);
}
if let Some(key) = merchant_key {
payment_request.sign(key).map_err(|e| {
PaymentError::ProcessingError(format!("Failed to sign payment request: {e}"))
})?;
}
let payment_id = Self::generate_payment_id(&payment_request);
self.payment_store
.lock()
.unwrap()
.insert(payment_id.clone(), payment_request.clone());
debug!("Created payment request: {}", payment_id);
Ok(payment_request)
}
pub async fn get_payment_request(
&self,
payment_id: &str,
) -> Result<PaymentRequest, PaymentError> {
let store = self.payment_store.lock().unwrap();
store
.get(payment_id)
.cloned()
.ok_or_else(|| PaymentError::RequestNotFound(payment_id.to_string()))
}
pub async fn process_payment(
&self,
payment: Payment,
payment_id: String,
merchant_key: Option<&secp256k1::SecretKey>,
) -> Result<PaymentACK, PaymentError> {
let request = self.get_payment_request(&payment_id).await?;
let ack = PaymentProtocolServer::process_payment(&payment, &request, merchant_key)
.map_err(|e| PaymentError::ValidationFailed(format!("{e:?}")))?;
info!("Processed payment: {}", payment_id);
if let Some(merchant_data) = &request.payment_details.merchant_data {
if let Ok(metadata) = serde_json::from_slice::<serde_json::Value>(merchant_data) {
if metadata
.get("payment_type")
.and_then(|v| v.as_str())
.map(|s| s == "module_payment")
.unwrap_or(false)
{
if let Err(e) = self
.encrypt_module_for_payment(&payment_id, &metadata)
.await
{
warn!("Failed to encrypt module for payment {}: {}", payment_id, e);
}
}
}
}
Ok(ack)
}
async fn encrypt_module_for_payment(
&self,
payment_id: &str,
metadata: &serde_json::Value,
) -> Result<(), PaymentError> {
let module_registry = self.module_registry.as_ref().ok_or_else(|| {
PaymentError::ProcessingError("Module registry not available".to_string())
})?;
let encryption = self.module_encryption.as_ref().ok_or_else(|| {
PaymentError::ProcessingError("Module encryption not available".to_string())
})?;
let modules_dir = self.modules_dir.as_ref().ok_or_else(|| {
PaymentError::ProcessingError("Modules directory not configured".to_string())
})?;
let module_name = metadata
.get("module_name")
.and_then(|v| v.as_str())
.ok_or_else(|| {
PaymentError::ProcessingError(
"Module name not found in payment metadata".to_string(),
)
})?;
let module_hash_hex = metadata
.get("module_hash")
.and_then(|v| v.as_str())
.ok_or_else(|| {
PaymentError::ProcessingError(
"Module hash not found in payment metadata".to_string(),
)
})?;
let module_hash = hex::decode(module_hash_hex)
.map_err(|e| PaymentError::ProcessingError(format!("Invalid module hash: {e}")))?
.try_into()
.map_err(|_| {
PaymentError::ProcessingError("Module hash must be 32 bytes".to_string())
})?;
let payment_method = if metadata
.get("payment_method")
.and_then(|v| v.as_str())
.is_some()
{
metadata
.get("payment_method")
.and_then(|v| v.as_str())
.unwrap_or("on-chain")
} else {
"on-chain" };
let entry = module_registry
.fetch_module(module_name)
.await
.map_err(|e| PaymentError::ProcessingError(format!("Failed to fetch module: {e}")))?;
let binary = entry.binary.ok_or_else(|| {
PaymentError::ProcessingError("Module binary not available".to_string())
})?;
let (encrypted_binary, nonce) = encryption
.encrypt_module(&binary, payment_id, &module_hash)
.map_err(|e| PaymentError::ProcessingError(format!("Encryption failed: {e}")))?;
use crate::module::encryption::EncryptedModuleMetadata;
let encrypted_at = crate::utils::current_timestamp();
let encryption_metadata = EncryptedModuleMetadata {
payment_id: payment_id.to_string(),
module_hash: module_hash.to_vec(),
nonce,
encrypted_at,
payment_method: payment_method.to_string(),
};
crate::module::encryption::store_encrypted_module(
modules_dir,
module_name,
&encrypted_binary,
&encryption_metadata,
)
.await
.map_err(|e| {
PaymentError::ProcessingError(format!("Failed to store encrypted module: {e}"))
})?;
info!(
"Module {} encrypted at rest (payment_id: {}, method: {})",
module_name, payment_id, payment_method
);
Ok(())
}
pub async fn create_module_payment_request(
&self,
manifest: &crate::module::registry::manifest::ModuleManifest,
module_hash: &[u8; 32],
node_script: Vec<u8>,
merchant_key: Option<&secp256k1::SecretKey>,
) -> Result<PaymentRequest, PaymentError> {
use crate::module::security::signing::ModuleSigner;
use blvm_protocol::address::BitcoinAddress;
let payment = manifest.payment.as_ref().ok_or_else(|| {
PaymentError::ProcessingError("Module manifest missing payment section".to_string())
})?;
if !payment.required {
return Err(PaymentError::ProcessingError(
"Module does not require payment".to_string(),
));
}
let price_sats = payment.price_sats.ok_or_else(|| {
PaymentError::ProcessingError("Payment required but price not specified".to_string())
})?;
fn derive_bip47_address(
_payment_code: &str,
_notification_index: u32,
) -> Result<String, PaymentError> {
Err(PaymentError::ProcessingError(
"BIP47 not yet implemented. Provide legacy address (author_address/commons_address) as fallback.".to_string(),
))
}
let author_address_str = if let Some(ref payment_code) = payment.author_payment_code {
match derive_bip47_address(payment_code, 0) {
Ok(addr) => {
info!("Derived BIP47 address for author payment: {}", addr);
addr
}
Err(_) => {
warn!("BIP47 derivation failed, falling back to legacy address");
payment.author_address.as_ref().ok_or_else(|| {
PaymentError::ProcessingError(
"BIP47 payment code provided but derivation failed, and legacy address not provided".to_string(),
)
})?.to_string()
}
}
} else if let Some(ref addr) = payment.author_address {
addr.to_string()
} else {
return Err(PaymentError::ProcessingError(
"Module author payment address or payment code not specified in manifest"
.to_string(),
));
};
let commons_address_str = if let Some(ref payment_code) = payment.commons_payment_code {
match derive_bip47_address(payment_code, 0) {
Ok(addr) => {
info!("Derived BIP47 address for commons payment: {}", addr);
addr
}
Err(_) => {
warn!("BIP47 derivation failed, falling back to legacy address");
payment.commons_address.as_ref().ok_or_else(|| {
PaymentError::ProcessingError(
"BIP47 payment code provided but derivation failed, and legacy address not provided".to_string(),
)
})?.to_string()
}
}
} else if let Some(ref addr) = payment.commons_address {
addr.to_string()
} else {
return Err(PaymentError::ProcessingError(
"Commons governance payment address or payment code not specified in manifest"
.to_string(),
));
};
if let Some(ref payment_sig) = payment.payment_signature {
let signer = ModuleSigner::new();
let public_keys = manifest.get_public_keys();
let threshold = manifest.get_threshold().ok_or_else(|| {
PaymentError::ProcessingError(
"Manifest signature threshold not specified".to_string(),
)
})?;
let valid = signer
.verify_payment_addresses(
&author_address_str,
&commons_address_str,
price_sats,
payment_sig,
&public_keys,
threshold,
)
.map_err(|e| {
PaymentError::ValidationFailed(format!(
"Payment address signature verification failed: {e}"
))
})?;
if !valid {
return Err(PaymentError::ValidationFailed(
"Payment address signature verification failed: insufficient signatures"
.to_string(),
));
}
debug!(
"Payment address signatures verified for module {}",
manifest.name
);
} else {
return Err(PaymentError::ValidationFailed(
"Payment addresses not cryptographically signed in manifest".to_string(),
));
}
let author_address = BitcoinAddress::decode(&author_address_str).map_err(|e| {
PaymentError::ProcessingError(format!("Invalid author address format: {e:?}"))
})?;
let marketplace_address = BitcoinAddress::decode(&commons_address_str).map_err(|e| {
PaymentError::ProcessingError(format!("Invalid marketplace address format: {e:?}"))
})?;
let author_script = address_to_script_pubkey(&author_address)?;
let marketplace_script = address_to_script_pubkey(&marketplace_address)?;
let author_amount = (price_sats * 75) / 100;
let marketplace_amount = (price_sats * 15) / 100;
let node_amount = (price_sats * 10) / 100;
let total = author_amount + marketplace_amount + node_amount;
if total > price_sats {
warn!(
"Payment split exceeds price: {} > {} (rounding error)",
total, price_sats
);
}
let outputs = vec![
PaymentOutput {
script: author_script,
amount: Some(author_amount),
},
PaymentOutput {
script: marketplace_script,
amount: Some(marketplace_amount),
},
PaymentOutput {
script: node_script,
amount: Some(node_amount),
},
];
let merchant_data = Some(
serde_json::to_vec(&json!({
"module_name": manifest.name,
"module_version": manifest.version,
"module_hash": hex::encode(module_hash),
"payment_type": "module_payment",
"price_sats": price_sats,
"split": {
"author": author_amount,
"marketplace": marketplace_amount,
"node": node_amount,
},
"author_address": author_address_str,
"commons_address": commons_address_str,
}))
.map_err(|e| {
PaymentError::ProcessingError(format!("Failed to serialize merchant_data: {e}"))
})?,
);
self.create_payment_request(outputs, merchant_data, merchant_key)
.await
}
}