Skip to main content

r402_http/server/
receipt.rs

1//! `Payment-Response` attach, failure-path receipts, skip-handler bodies.
2
3use axum_core::body::Body;
4use axum_core::response::Response;
5use http::header::CONTENT_TYPE;
6use http::{HeaderValue, StatusCode};
7use r402_protocol::payment::SettleResponse;
8use r402_server::{
9    CompletedSettlement, SkipHandlerDirective, WirePaymentPayload,
10    build_failure_path_settlement_response,
11};
12
13use super::fail::GateError;
14use crate::headers::{PAYMENT_RESPONSE, ensure_expose_headers, merge_private};
15
16/// Encodes a successful settlement as `Payment-Response`.
17///
18/// # Errors
19///
20/// [`GateError::SettlementAborted`] when the response is not success or encoding fails.
21pub fn settlement_to_header(settlement: &SettleResponse) -> Result<HeaderValue, GateError> {
22    let encoded = settlement
23        .encode_base64()
24        .ok_or_else(|| GateError::SettlementAborted("cannot encode error settlement".to_owned()))?;
25    HeaderValue::from_bytes(encoded.as_ref())
26        .map_err(|e| GateError::SettlementAborted(e.to_string()))
27}
28
29pub(crate) fn attach_payment_response(
30    response: &mut Response,
31    settlement: &SettleResponse,
32) -> Result<(), GateError> {
33    let header_value = settlement_to_header(settlement)?;
34    let _ = response
35        .headers_mut()
36        .insert(PAYMENT_RESPONSE, header_value);
37    ensure_expose_headers(response.headers_mut());
38    merge_private(response.headers_mut());
39    Ok(())
40}
41
42pub(crate) fn attach_failure_path_settlement(
43    response: &mut Response,
44    cancel_settlement: Option<&SettleResponse>,
45    before_handler: Option<&CompletedSettlement>,
46    payment_payload: Option<&WirePaymentPayload>,
47) -> Result<(), GateError> {
48    let Some(receipt) =
49        build_failure_path_settlement_response(cancel_settlement, before_handler, payment_payload)
50    else {
51        return Ok(());
52    };
53    let encoded = receipt.encode_base64_any().ok_or_else(|| {
54        GateError::SettlementAborted("cannot encode failure-path settlement".to_owned())
55    })?;
56    let header_value = HeaderValue::from_bytes(encoded.as_ref())
57        .map_err(|e| GateError::SettlementAborted(e.to_string()))?;
58    let _ = response
59        .headers_mut()
60        .insert(PAYMENT_RESPONSE, header_value);
61    ensure_expose_headers(response.headers_mut());
62    Ok(())
63}
64
65pub(crate) fn skip_handler_response(
66    directive: &SkipHandlerDirective,
67    settlement: &SettleResponse,
68) -> Result<Response, GateError> {
69    let content_type = directive
70        .content_type
71        .as_deref()
72        .unwrap_or("application/json");
73    let body_bytes = directive.body.as_ref().map_or_else(
74        || b"null".to_vec(),
75        |value| serde_json::to_vec(value).unwrap_or_else(|_| b"null".to_vec()),
76    );
77    let header_value = settlement_to_header(settlement)?;
78    let mut response = Response::new(Body::from(body_bytes));
79    *response.status_mut() = StatusCode::OK;
80    if let Ok(ct) = HeaderValue::from_str(content_type) {
81        let _ = response.headers_mut().insert(CONTENT_TYPE, ct);
82    }
83    let _ = response
84        .headers_mut()
85        .insert(PAYMENT_RESPONSE, header_value);
86    ensure_expose_headers(response.headers_mut());
87    merge_private(response.headers_mut());
88    Ok(response)
89}