Skip to main content

r402_http/server/
verify.rs

1//! Steps 3–5: extract `Payment-Signature`, match, verify, skip-handler, settle-before.
2
3use http::HeaderMap;
4use r402_facilitator::DynFacilitator;
5use r402_protocol::error::FacilitatorError;
6use r402_protocol::payment::{
7    Base64Bytes, Extensions, PaymentRequired, PaymentRequirements, SettleResponse,
8    SettlementOverrides, VerifyResponse,
9};
10use r402_server::{
11    CancelReason, CompletedSettlement, PaymentFlowName, PaymentFlowPhases,
12    PaymentRequiredBuildContext, ResourceServer, SettlePhase, SkipHandlerDirective,
13    WirePaymentPayload, resolve_payment_flow_phases, validate_accepts_against_supported,
14};
15
16use super::fail::GateError;
17use super::gate::Gate;
18use crate::headers::PAYMENT_SIGNATURE;
19
20/// Verified payment ready for settle. Consumed by after-handler settle.
21#[derive(Debug, Clone)]
22pub struct VerifiedPayment {
23    pub(crate) payload: WirePaymentPayload,
24    pub(crate) requirements: PaymentRequirements,
25    pub(crate) server: ResourceServer,
26    pub(crate) skip_handler: Option<SkipHandlerDirective>,
27    pub(crate) resource_url: compact_str::CompactString,
28    pub(crate) advertised: Extensions,
29}
30
31impl VerifiedPayment {
32    /// One-shot cancel dispatcher.
33    #[must_use]
34    pub fn cancellation_guard(&self) -> r402_server::CancellationGuard {
35        self.server
36            .cancellation_guard(self.payload.clone(), self.requirements.clone())
37    }
38
39    /// Matched requirements (override resolution).
40    #[must_use]
41    pub const fn requirements(&self) -> &PaymentRequirements {
42        &self.requirements
43    }
44
45    pub(crate) async fn settle_phase(
46        &self,
47        phase: SettlePhase,
48        overrides: Option<&SettlementOverrides>,
49    ) -> Result<SettleResponse, FacilitatorError> {
50        self.server
51            .settle_payment(
52                &self.payload,
53                &self.requirements,
54                overrides,
55                phase,
56                Some(self.resource_url.as_str()),
57                Some(&self.advertised),
58            )
59            .await
60    }
61}
62
63impl Gate {
64    /// Fetches `/supported` and builds the shared 402 body.
65    ///
66    /// # Errors
67    ///
68    /// [`GateError::Transport`] when `/supported` fails.
69    /// [`GateError::FacilitatorSupport`] when this accept list has no matching
70    /// kind or advertised extra is unusable.
71    /// [`GateError::PaymentRequiredBuild`] when 402 construction fails.
72    pub async fn build_payment_required(&mut self) -> Result<(), GateError> {
73        let facilitator = self.server.facilitator();
74        let supported = DynFacilitator::supported(facilitator.as_ref())
75            .await
76            .map_err(GateError::from_verify_facilitator)?;
77        let reqs: Vec<_> = self
78            .accepts
79            .iter()
80            .map(|pt| pt.requirements.clone())
81            .collect();
82        validate_accepts_against_supported(&self.server, &reqs, &supported)
83            .map_err(GateError::FacilitatorSupport)?;
84        let built = self
85            .server
86            .create_payment_required_response(
87                reqs,
88                PaymentRequiredBuildContext {
89                    resource: self.resource.clone(),
90                    error: None,
91                    extensions: Extensions::new(),
92                    supported,
93                    payment_payload: None,
94                },
95            )
96            .await
97            .map_err(|err| GateError::PaymentRequiredBuild(err.to_string()))?;
98        self.payment_required = Some(built);
99        Ok(())
100    }
101
102    /// Verifies `Payment-Signature` against the built 402 accepts.
103    ///
104    /// # Errors
105    ///
106    /// Missing/malformed header → 402. Transport → 502. `isValid: false` → 402/412.
107    pub async fn verify_only(&self, headers: &HeaderMap) -> Result<VerifiedPayment, GateError> {
108        let header_bytes = headers
109            .get(PAYMENT_SIGNATURE)
110            .map(http::HeaderValue::as_bytes)
111            .ok_or(GateError::PaymentHeaderMissing)?;
112
113        let payload =
114            decode_payment_payload(header_bytes).ok_or(GateError::InvalidPaymentHeader)?;
115
116        let payment_required: &PaymentRequired =
117            self.payment_required.as_ref().ok_or_else(|| {
118                GateError::PaymentRequiredBuild(
119                    "payment-required response has not been built".into(),
120                )
121            })?;
122        let requirements = self
123            .server
124            .find_matching_requirements(&payment_required.accepts, &payload)
125            .cloned()
126            .ok_or(GateError::NoPaymentMatching)?;
127        let outcome = self
128            .server
129            .verify_payment(&payload, &requirements, Some(&payment_required.extensions))
130            .await
131            .map_err(GateError::from_verify_facilitator)?;
132
133        if let VerifyResponse::Invalid {
134            reason, message, ..
135        } = &outcome.response
136        {
137            return Err(GateError::from_invalid_verify(
138                reason.clone(),
139                message.as_deref(),
140            ));
141        }
142
143        Ok(VerifiedPayment {
144            payload,
145            requirements,
146            server: self.server.clone(),
147            skip_handler: outcome.skip_handler,
148            resource_url: payment_required.resource.url.clone(),
149            advertised: payment_required.extensions.clone(),
150        })
151    }
152}
153
154pub(crate) fn decode_payment_payload(header_bytes: &[u8]) -> Option<WirePaymentPayload> {
155    let decoded = Base64Bytes::from(header_bytes).decode().ok()?;
156    serde_json::from_slice(decoded.as_ref()).ok()
157}
158
159pub(crate) fn payment_flow_of(
160    server: &ResourceServer,
161    requirements: &PaymentRequirements,
162) -> Result<PaymentFlowName, GateError> {
163    server
164        .get_payment_flow(requirements)
165        .map_err(|err| match err {
166            r402_server::PaymentFlowError::UnregisteredScheme { scheme, .. } => {
167                GateError::MissingScheme {
168                    scheme: scheme.into(),
169                    network: requirements.network.clone(),
170                }
171            }
172            other => GateError::PaymentRequiredBuild(other.to_string()),
173        })
174}
175
176pub(crate) fn cancellation_guard(
177    verified: &VerifiedPayment,
178    before_handler: Option<&CompletedSettlement>,
179) -> r402_server::CancellationGuard {
180    let guard = verified.cancellation_guard();
181    match before_handler {
182        Some(completed) => guard
183            .with_settled_phases([SettlePhase::BeforeHandler])
184            .with_before_handler(completed.clone()),
185        None => guard,
186    }
187}
188
189pub(crate) async fn settle_before_handler_if_needed(
190    verified: &VerifiedPayment,
191    flow: PaymentFlowName,
192    phases: PaymentFlowPhases,
193) -> Result<Option<CompletedSettlement>, GateError> {
194    if !phases.settle_before_handler {
195        return Ok(None);
196    }
197    let result = map_settle(
198        verified
199            .settle_phase(SettlePhase::BeforeHandler, None)
200            .await,
201    )?;
202    Ok(Some(CompletedSettlement::new(
203        SettlePhase::BeforeHandler,
204        flow,
205        result,
206        verified.requirements().clone(),
207    )))
208}
209
210pub(crate) fn map_settle(
211    result: Result<SettleResponse, FacilitatorError>,
212) -> Result<SettleResponse, GateError> {
213    let settlement = result.map_err(GateError::from_settle_facilitator)?;
214    if matches!(settlement, SettleResponse::Failure { .. }) {
215        return Err(GateError::Settlement(Box::new(settlement)));
216    }
217    Ok(settlement)
218}
219
220pub(crate) fn phases_of(
221    verified: &VerifiedPayment,
222) -> Result<(PaymentFlowName, PaymentFlowPhases), GateError> {
223    let flow = payment_flow_of(&verified.server, verified.requirements())?;
224    Ok((flow, resolve_payment_flow_phases(flow)))
225}
226
227/// Reason string for handler-failed cancel.
228pub(crate) const HANDLER_FAILED: &str = "handler returned error status";
229
230pub(crate) const fn handler_failed_reason() -> CancelReason {
231    CancelReason::HandlerFailed
232}