use compact_str::CompactString;
use r402_protocol::error::FacilitatorError;
use r402_protocol::extension::{AdvertiseContext, ExtensionRegistry};
use r402_protocol::network::ChainId;
use r402_protocol::payment::{
Extensions, PaymentRequired, PaymentRequirements, ResourceInfo, SupportedResponse,
};
use crate::hooks::{
WirePaymentPayload, assert_accepts_additive_extra_after_scheme_enrich,
assert_accepts_allowlisted_after_extension_enrich, snapshot_payment_requirements_list,
};
use crate::payment_flow::apply_payment_flow_wire_extra;
use crate::resource::ResourceServer;
use crate::scheme::SchemePaymentRequiredContext;
#[derive(Debug, Clone)]
pub struct PaymentRequiredBuildContext {
pub resource: ResourceInfo,
pub error: Option<CompactString>,
pub extensions: Extensions,
pub supported: SupportedResponse,
pub payment_payload: Option<WirePaymentPayload>,
}
impl ResourceServer {
pub async fn create_payment_required_response(
&self,
accepts: Vec<PaymentRequirements>,
ctx: PaymentRequiredBuildContext,
) -> Result<PaymentRequired, FacilitatorError> {
let PaymentRequiredBuildContext {
resource,
error,
extensions,
supported,
payment_payload,
} = ctx;
let mut response = PaymentRequired::new(resource)
.with_accepts(accepts)
.with_extensions(extensions);
if let Some(error) = error {
response = response.with_error(error);
}
self.apply_scheme_payment_required_enrich(
&mut response,
&supported,
payment_payload.as_ref(),
)
.await?;
advertise_registered_extensions(&self.extensions, &mut response).await?;
self.apply_payment_flow_extras(&mut response.accepts)?;
Ok(response)
}
fn apply_payment_flow_extras(
&self,
accepts: &mut [PaymentRequirements],
) -> Result<(), FacilitatorError> {
for accept in accepts {
let resolved = self
.resolved_payment_flow(accept)
.map_err(FacilitatorError::internal)?;
let next = apply_payment_flow_wire_extra(accept.extra.as_ref(), &resolved);
accept.extra = if next.is_empty() {
None
} else {
Some(serde_json::Value::Object(next))
};
}
Ok(())
}
async fn apply_scheme_payment_required_enrich(
&self,
response: &mut PaymentRequired,
supported: &SupportedResponse,
payment_payload: Option<&WirePaymentPayload>,
) -> Result<(), FacilitatorError> {
let targets: Vec<(CompactString, ChainId)> = response
.accepts
.iter()
.map(|accept| (accept.scheme.clone(), accept.network.clone()))
.collect();
let mut baseline = snapshot_payment_requirements_list(&response.accepts);
for (scheme_name, network) in targets {
let Some(scheme) = self.registered_scheme(scheme_name.as_str(), &network) else {
continue;
};
let network_label = network.to_string();
let enriched = {
let ctx = SchemePaymentRequiredContext {
requirements: &response.accepts,
payment_payload,
resource: &response.resource,
error: response.error.as_deref(),
payment_required_response: response,
supported,
network: &network,
};
scheme.enrich_payment_required_response(&ctx).await
};
if let Some(accepts) = enriched {
response.accepts = accepts;
}
assert_accepts_additive_extra_after_scheme_enrich(
&baseline,
&response.accepts,
scheme_name.as_str(),
network_label.as_str(),
)
.map_err(FacilitatorError::internal)?;
baseline = snapshot_payment_requirements_list(&response.accepts);
}
Ok(())
}
}
async fn advertise_registered_extensions(
registry: &ExtensionRegistry,
response: &mut PaymentRequired,
) -> Result<(), FacilitatorError> {
for ext in registry.iter() {
let baseline = snapshot_payment_requirements_list(&response.accepts);
let existing = response.extensions.get(ext.id()).cloned();
let ctx = AdvertiseContext::for_payment_required(
&response.resource,
&response.accepts,
existing.as_ref(),
);
if existing.is_none()
&& let Some(entry) = ext.advertise(&ctx)
{
response.extensions.insert(ext.id(), entry);
}
if let Some(entry) = ext.enrich_payment_required(&ctx).await {
response.extensions.insert(ext.id(), entry);
}
assert_accepts_allowlisted_after_extension_enrich(&baseline, &response.accepts, ext.id())
.map_err(FacilitatorError::internal)?;
}
Ok(())
}