# @rma-dev/http-proxy-util

TypeScript library that wraps any HTTP API behind machine payments.

```bash
npm install @rma-dev/http-proxy-util mppx stripe
```

---

## The minimum

Seven lines. Any unpaid request gets a 402 with a Stripe payment challenge; paid requests are forwarded to your origin.

```ts
import { createProxy } from '@rma-dev/http-proxy-util'

const proxy = createProxy({
  stripe: { secretKey: process.env.STRIPE_SECRET_KEY! },
  origin: 'https://api.yourservice.com',
  routes: [{ method: 'POST', path: '/v1/infer', amount: '0.05' }],
})
Bun.serve({ fetch: proxy.fetch })
```

---

## Multiple routes

Wildcard `*` matches one path segment. Unlisted routes are forwarded free.

```ts
const proxy = createProxy({
  stripe: { secretKey: process.env.STRIPE_SECRET_KEY! },
  origin: 'https://api.yourservice.com',
  routes: [
    { method: 'POST', path: '/v1/infer',  amount: '0.05', description: 'Single inference' },
    { method: 'GET',  path: '/v1/data/*', amount: '0.01' },
  ],
})
```

---

## Add Tempo (crypto) payments

Include a `tempo` block to accept PATH stablecoin alongside Stripe. Callers see both options and pick whichever their wallet supports.

```ts
const proxy = createProxy({
  stripe: { secretKey: process.env.STRIPE_SECRET_KEY! },
  tempo:  { recipient: process.env.TEMPO_RECIPIENT as `0x${string}`, testnet: true },
  origin: 'https://api.yourservice.com',
  routes: [
    {
      method: 'POST',
      path:   '/v1/infer',
      pricing: {
        stripe: { amount: '0.05', currency: 'usd' },
        tempo:  { amount: '0.04' },   // slightly cheaper to incentivise crypto
      },
    },
  ],
})
```

---

## Add Base USDC payments

```ts
const proxy = createProxy({
  stripe: { secretKey: process.env.STRIPE_SECRET_KEY! },
  base: {
    payTo:   process.env.BASE_PAY_TO as `0x${string}`,
    network: 'base-sepolia',   // 'base' for mainnet
  },
  origin: 'https://api.yourservice.com',
  routes: [
    {
      method: 'POST',
      path:   '/v1/infer',
      pricing: {
        stripe: { amount: '0.05', currency: 'usd' },
        base:   { amount: '0.05' },
      },
    },
  ],
})
```

---

## All three payment methods

Accept Stripe card, Tempo crypto, and Base USDC simultaneously.

```ts
const proxy = createProxy({
  stripe: { secretKey: process.env.STRIPE_SECRET_KEY! },
  tempo:  { recipient: process.env.TEMPO_RECIPIENT as `0x${string}`, testnet: true },
  base:   { payTo: process.env.BASE_PAY_TO as `0x${string}`, network: 'base-sepolia' },
  origin: 'https://api.yourservice.com',
  routes: [
    {
      method:      'POST',
      path:        '/v1/infer',
      description: 'Single inference request',
      pricing: {
        stripe: { amount: '0.05', currency: 'usd' },
        tempo:  { amount: '0.04' },
        base:   { amount: '0.05' },
      },
    },
    {
      method: 'GET',
      path:   '/v1/data/*',
      pricing: {
        stripe: { amount: '0.01', currency: 'usd' },
        tempo:  { amount: '0.008' },
        base:   { amount: '0.01' },
      },
    },
  ],
})
```

---

## Framework adapters

All adapters delegate to `proxy.fetch`.

```ts
// Bun
Bun.serve({ port: 3000, fetch: proxy.fetch })

// Hono
app.all('*', (c) => proxy.fetch(c.req.raw))

// Express
app.use(proxy.express())

// Next.js App Router  (app/api/[[...path]]/route.ts)
export const { GET, POST, PUT, PATCH, DELETE } = proxy.nextjs()

// Cloudflare Workers / Deno
export default { fetch: proxy.fetch }
```

---

## Built-in endpoints

| Path | Description |
|---|---|
| `/llms.txt` | Human/LLM-readable pricing and billing info |
| `/__stripe-proxy/discover` | JSON pricing discovery per route |
| `/__stripe-proxy/health` | Liveness probe |
| `/api/stripe-proxy/create-spt` | Test SPT for browser flows (test mode only) |

---

## `_meta` in request bodies

The proxy reads a `_meta` object from JSON bodies for customer enrichment, then forwards the full body — including `_meta` — to the origin unchanged.

```json
{
  "prompt": "Hello",
  "_meta": {
    "email": "user@example.com",
    "payment_intent_metadata": { "tenant": "acme" }
  }
}
```

---

## Configuration reference

### `createProxy(options)`

| Option | Type | Description |
|---|---|---|
| `stripe` | `StripeConfig?` | Stripe card / Link payments |
| `tempo` | `TempoConfig?` | Tempo crypto (PATH stablecoin) payments |
| `base` | `BaseConfig?` | Base USDC payments |
| `origin` | `string` | Upstream service base URL |
| `routes` | `RoutePriceConfig[]` | Per-route pricing table |
| `services` | `Record<string, ServiceConfig>?` | Named sub-services at `/{id}/*` |
| `title` | `string?` | Proxy name — surfaced in `/llms.txt` |
| `description` | `string?` | Short description — surfaced in `/llms.txt` |
| `blockUnlistedRoutes` | `boolean?` | 404 for routes not in the table (default `false`) |
| `paymentMetadata` | `Record<string, string>?` | Static metadata on every PaymentIntent |
| `createCustomers` | `boolean?` | Stripe customer reconciliation (default `true`) |
| `sptEndpoint` | `string \| null?` | Test SPT path (default `/api/stripe-proxy/create-spt`) |

### `StripeConfig`

| Field | Type | Description |
|---|---|---|
| `secretKey` | `string` | `sk_test_…` or `sk_live_…` |
| `networkId` | `string?` | Stripe Business Network profile ID (`'internal'` for test) |
| `paymentMethodTypes` | `string[]?` | Default: `['card', 'link']` |

### `TempoConfig`

| Field | Type | Description |
|---|---|---|
| `recipient` | `` `0x${string}` `` | Wallet address that receives PATH payments |
| `testnet` | `boolean?` | Use Tempo testnet (default `false`) |
| `currency` | `string?` | PATH token address (defaults to PATH_USD) |

### `BaseConfig`

| Field | Type | Description |
|---|---|---|
| `payTo` | `` `0x${string}` `` | EVM wallet that receives USDC |
| `network` | `string?` | `'base'`, `'base-sepolia'` (default), or EIP-155 chain ID |
| `facilitatorUrl` | `string?` | Payment verification URL (default: `https://x402.org/facilitator`) |

### `RoutePriceConfig`

| Field | Type | Description |
|---|---|---|
| `method` | `string` | HTTP method or `'*'` |
| `path` | `string` | URL path — supports a single `*` wildcard per segment |
| `description` | `string?` | Human-readable label in the payment challenge |
| `amount` | `string?` | Flat Stripe price in USD (e.g. `'0.05'`) |
| `currency` | `string?` | Currency for flat `amount` (default `'usd'`) |
| `pricing` | `RoutePricing?` | Per-method pricing (overrides `amount`/`currency`) |
| `free` | `boolean?` | Forward without a payment gate |
| `requireEmail` | `boolean?` | Require `_meta.email` in the request body |
| `requirePhone` | `boolean?` | Require `_meta.phone` in the request body |
| `requireBillingAddress` | `boolean?` | Require `_meta.billing_address` |
| `schema` | `RouteSchema?` | Zod-compatible validators for `body`/`query` |

### `RoutePricing`

| Field | Type | Description |
|---|---|---|
| `stripe` | `{ amount: string; currency: string }?` | Stripe card price |
| `tempo` | `{ amount: string }?` | Tempo price in PATH units |
| `base` | `{ amount: string }?` | Base price in USD (converted to USDC internally) |

## Peer dependencies

```
mppx   >=0.6.0
stripe >=17.0.0
```
