mpay 0.2.0

Machine Payment Protocol - Rust library for Web Payment Auth
Documentation
# hyper Low-Level Example

Using `mpay` with [hyper](https://docs.rs/hyper) for low-level HTTP handling.

## Dependencies

```toml
[dependencies]
mpay = "0.1"
hyper = { version = "1", features = ["full"] }
hyper-util = { version = "0.1", features = ["full"] }
http-body-util = "0.1"
tokio = { version = "1", features = ["full"] }
```

## Client-Side

```rust
use hyper::{body::Incoming, Request, Response};
use hyper_util::client::legacy::Client;
use mpay::{parse_www_authenticate, PaymentCredential, PaymentPayload, format_authorization};

async fn request_with_payment(
    client: &Client<HttpConnector, String>,
    uri: &str,
) -> Result<Response<Incoming>, Box<dyn std::error::Error>> {
    // Initial request
    let req = Request::get(uri).body(String::new())?;
    let resp = client.request(req).await?;

    if resp.status() == hyper::StatusCode::PAYMENT_REQUIRED {
        // Extract and parse challenge
        let header = resp
            .headers()
            .get("www-authenticate")
            .ok_or("missing challenge")?
            .to_str()?;

        let challenge = parse_www_authenticate(header)?;

        // Execute payment
        let tx_hash = pay(&challenge).await?;

        // Build credential
        let credential = PaymentCredential::with_source(
            challenge.to_echo(),
            "did:pkh:eip155:8453:0x...",
            PaymentPayload::hash(&tx_hash),
        );

        // Retry with authorization
        let req = Request::get(uri)
            .header("authorization", format_authorization(&credential)?)
            .body(String::new())?;

        return Ok(client.request(req).await?);
    }

    Ok(resp)
}
```

## Server-Side

```rust
use hyper::{body::Incoming, server::conn::http1, service::service_fn, Method, Request, Response};
use mpay::{
    PaymentChallenge, Receipt, Base64UrlJson,
    parse_authorization, format_www_authenticate, format_receipt,
};

async fn handle_request(
    req: Request<Incoming>,
) -> Result<Response<String>, hyper::Error> {
    match (req.method(), req.uri().path()) {
        (&Method::GET, "/paid") => handle_paid_request(req).await,
        _ => Ok(Response::new("Not found".into())),
    }
}

async fn handle_paid_request(
    req: Request<Incoming>,
) -> Result<Response<String>, hyper::Error> {
    // Check authorization header
    if let Some(auth) = req.headers().get("authorization") {
        if let Ok(auth_str) = auth.to_str() {
            if let Ok(credential) = parse_authorization(auth_str) {
                // Verify payment
                if verify(&credential).await {
                    let receipt = Receipt::success("tempo", "0x...");

                    let resp = Response::builder()
                        .status(200)
                        .header("payment-receipt", format_receipt(&receipt).unwrap())
                        .body("Paid content".into())
                        .unwrap();

                    return Ok(resp);
                }
            }
        }
    }

    // Return 402 with challenge
    let challenge = PaymentChallenge {
        id: uuid::Uuid::new_v4().to_string(),
        realm: "api.example.com".into(),
        method: "tempo".into(),
        intent: "charge".into(),
        request: Base64UrlJson::from_value(&serde_json::json!({
            "amount": "1000000",
            "currency": "0x...",
            "recipient": "0x...",
        })).unwrap(),
        expires: None,
        description: None,
    };

    let resp = Response::builder()
        .status(402)
        .header("www-authenticate", format_www_authenticate(&challenge).unwrap())
        .body("Payment required".into())
        .unwrap();

    Ok(resp)
}
```

## Running the Server

```rust
use hyper_util::rt::TokioIo;
use std::net::SocketAddr;
use tokio::net::TcpListener;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    let listener = TcpListener::bind(addr).await?;

    loop {
        let (stream, _) = listener.accept().await?;
        let io = TokioIo::new(stream);

        tokio::spawn(async move {
            if let Err(err) = http1::Builder::new()
                .serve_connection(io, service_fn(handle_request))
                .await
            {
                eprintln!("Error: {:?}", err);
            }
        });
    }
}
```