mpay 0.2.0

Machine Payment Protocol - Rust library for Web Payment Auth
Documentation
# axum Server Example

Using `mpay` with [axum](https://docs.rs/axum) for server-side payment gating.

## Dependencies

```toml
[dependencies]
mpay = "0.1"
axum = "0.7"
tokio = { version = "1", features = ["full"] }
serde_json = "1"
uuid = { version = "1", features = ["v4"] }
```

## Basic Payment-Gated Endpoint

```rust
use axum::{
    extract::State,
    http::{header, HeaderMap, StatusCode},
    response::IntoResponse,
};
use mpay::{ChargeRequest, parse_authorization};
use mpay::server::{Mpay, tempo_provider, TempoChargeMethod};
use std::sync::Arc;

// Create payment handler at startup
let provider = tempo_provider("https://rpc.moderato.tempo.xyz")?;
let method = TempoChargeMethod::new(provider);
let payment = Arc::new(Mpay::new(method, "api.example.com", "my-server-secret"));

async fn paid_endpoint(
    State(payment): State<Arc<Mpay<TempoChargeMethod<_>>>>,
    headers: HeaderMap,
) -> impl IntoResponse {
    let request = ChargeRequest {
        amount: "1000000".into(),
        currency: "0x...".into(),
        recipient: Some("0x...".into()),
        ..Default::default()
    };

    // Check for payment credential
    if let Some(auth) = headers.get(header::AUTHORIZATION) {
        if let Ok(auth_str) = auth.to_str() {
            if let Ok(credential) = parse_authorization(auth_str) {
                // Verify the payment
                if let Ok(receipt) = payment.verify(&credential, &request).await {
                    return (
                        StatusCode::OK,
                        [("payment-receipt", receipt.to_header().unwrap())],
                        "Here's your paid content!",
                    );
                }
            }
        }
    }

    // No valid payment - return 402 with challenge
    let challenge = payment.charge_challenge("1000000", "0x...", "0x...").unwrap();

    (
        StatusCode::PAYMENT_REQUIRED,
        [(header::WWW_AUTHENTICATE, challenge.to_header().unwrap())],
        "Payment required",
    )
}
```

## With Extractor

```rust
use axum::{
    async_trait,
    extract::FromRequestParts,
    http::{request::Parts, StatusCode},
};
use mpay::{PaymentCredential, parse_authorization};

/// Extractor that requires a valid payment credential
struct RequirePayment(PaymentCredential);

#[async_trait]
impl<S> FromRequestParts<S> for RequirePayment
where
    S: Send + Sync,
{
    type Rejection = (StatusCode, String);

    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
        let auth = parts
            .headers
            .get("authorization")
            .and_then(|h| h.to_str().ok())
            .ok_or((StatusCode::PAYMENT_REQUIRED, "missing authorization".into()))?;

        let credential = parse_authorization(auth)
            .map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;

        // Verify payment here...

        Ok(RequirePayment(credential))
    }
}

// Usage
async fn handler(RequirePayment(credential): RequirePayment) -> impl IntoResponse {
    format!("Paid by: {:?}", credential.source)
}
```

## Router Setup

```rust
use axum::{routing::get, Router};

fn app() -> Router {
    Router::new()
        .route("/free", get(|| async { "Free content" }))
        .route("/paid", get(paid_endpoint))
        .route("/premium", get(premium_endpoint))
}
```

## Dynamic Pricing

```rust
async fn dynamic_pricing(
    State(payment): State<Arc<PaymentHandler>>,
    Path(resource_id): Path<String>,
) -> impl IntoResponse {
    // Look up price for this resource
    let price = get_resource_price(&resource_id).await;

    // Generate challenge with dynamic pricing (secret_key already bound)
    let challenge = payment
        .charge_challenge(&price.to_string(), USDC_ADDRESS, MERCHANT_ADDRESS)
        .unwrap();

    (
        StatusCode::PAYMENT_REQUIRED,
        [(header::WWW_AUTHENTICATE, challenge.to_header().unwrap())],
        "Payment required",
    )
}
```