Metadata-Version: 2.4
Name: fastmpp
Version: 0.1.0
Summary: MPP-based payment gating for FastAPI
Requires-Python: >=3.14
Description-Content-Type: text/markdown
Requires-Dist: fastapi[standard]>=0.135.2
Requires-Dist: pympp[tempo]>=0.4.2
Requires-Dist: starlette>=0.45.0

# fastapi_mpp

MPP-based payment gating for FastAPI. Add a single decorator to any route and it handles the full 402 → pay → verify flow automatically.

## Quick start

```python
from fastapi import FastAPI
from fastapi_mpp import ChargeableFactory, PaymentMethod

app = FastAPI()

factory = ChargeableFactory.create(
    PaymentMethod.tempo(
        currency="0x20C000...",   # USDC token address
        recipient="0xYourWallet",
    ),
    realm="my-api",               # shown in WWW-Authenticate
    secret_key="...",             # or set MPP_SECRET_KEY env var
)

@app.get("/premium")
@factory.Chargeable(amount="0.50", description="Premium data")
async def premium():
    return {"data": "paid content"}
```

**Without a valid payment credential** the route returns:

```
HTTP/1.1 402 Payment Required
WWW-Authenticate: MPP realm="my-api", ...

{"error": "Payment Required", "challenge": {...}}
```

**With a valid credential** the route runs and responds:

```
HTTP/1.1 200 OK
Payment-Receipt: MPP receipt=...

{"data": "paid content"}
```

---

## Factory config reference

`ChargeableFactory.create(*methods, realm=None, secret_key=None)`

| Parameter | Type | Description |
|---|---|---|
| `*methods` | positional | Exactly one `PaymentMethod.*()` result. Multi-method support is planned. |
| `realm` | `str \| None` | Server realm for `WWW-Authenticate`. Falls back to `MPP_REALM` env var. |
| `secret_key` | `str \| None` | HMAC secret for challenge signing. Falls back to `MPP_SECRET_KEY` env var. |

---

## `PaymentMethod.tempo()` config reference

| Parameter | Type | Default | Description |
|---|---|---|---|
| `currency` | `str \| None` | `None` | Default currency token address (e.g. USDC). |
| `recipient` | `str \| None` | `None` | Default recipient wallet address. |
| `decimals` | `int` | `6` | Token decimal places. |
| `rpc_url` | `str \| None` | `None` | Override the Tempo RPC endpoint. |
| `chain_id` | `int \| None` | `None` | `4217` = mainnet, `42431` = testnet (Moderato). |
| `fee_payer_key` | `str \| None` | `None` | Hex private key (`0x`-prefixed) for gas-sponsored transactions. |
| `root_account` | `str \| None` | `None` | Root account address for access key signing. |
| `client_id` | `str \| None` | `None` | Optional client identity for attribution memos. |
| `enable_store` | `bool` | `False` | Enable in-memory replay protection (prevents credential reuse). |

---

## Decorator config reference

`@factory.Chargeable(amount, *, ...)`

| Parameter | Type | Default | Description |
|---|---|---|---|
| `amount` | `str` | **required** | Human-readable charge amount, e.g. `"0.50"`. |
| `currency` | `str \| None` | `None` | Override the method-level default currency for this route. |
| `recipient` | `str \| None` | `None` | Override the method-level default recipient for this route. |
| `description` | `str \| None` | `None` | Human-readable payment description shown to the payer. |
| `expires` | `str \| None` | `None` | ISO 8601 challenge expiry. Defaults to now + 5 minutes. |
| `attach_receipt` | `bool` | `True` | Set `Payment-Receipt` header on successful payment. |
| `on_payment` | `async (credential, receipt) -> None` | `None` | Async callback fired after each successful payment. |

---

## Optional middleware

`MppMiddleware` attaches the `Mpp` instance to `request.state.mpp` for advanced use cases:

```python
from fastapi_mpp import MppMiddleware

app.add_middleware(MppMiddleware, factory=factory)

@app.get("/advanced")
async def route(request: Request):
    mpp = request.state.mpp
    result = await mpp.charge(
        authorization=request.headers.get("Authorization"),
        amount="1.00",
    )
    ...
```

---

## Testing with npx mppx

[`mppx`](https://www.npmjs.com/package/mppx) is the MPP CLI test client. Run your FastAPI app locally, then:

```bash
# Install once
npm install -g mppx

# Hit a gated endpoint — mppx handles the 402 → wallet → credential flow
npx mppx http://localhost:8000/premium

# Specify a wallet private key
npx mppx --key 0xYourPrivateKey http://localhost:8000/premium

# Testnet (Moderato)
npx mppx --network moderato http://localhost:8000/premium
```

Start the example app:

```bash
export MPP_SECRET_KEY=dev-secret
export MPP_REALM=example-api
export TEMPO_CURRENCY=0x20c0000000000000000000000000000000000000   # Tempo PATH/USD (testnet)
export TEMPO_RECIPIENT=0xYourWalletAddress

uv run fastapi dev examples/basic.py
```

---

## Running tests

```bash
uv sync --group dev
uv run pytest
```

Run a single test file:

```bash
uv run pytest tests/test_402_flow.py -v
```
